一 Java中线程的实现
(1)通过继承Thread类
package javase.thread;class MyThread extends Thread{ public void run(){ for(int i=0;i<10;i++) System.out.println("正在运行线程:" + currentThread().getName()); }}public class ThreadDemo { public static void main(String[] args) { MyThread thread1 = new MyThread(); MyThread thread2 = new MyThread(); thread1.start(); thread2.start(); }}
(2)通过实现Runnable接口
package javase.thread;class MyRunnable implements Runnable{ public void run() { for(int i=0;i<10;i++) System.out.println("正在运行线程: " + Thread.currentThread().getName()); } }public class RunnableDemo { public static void main(String[] args) { MyRunnable runnable1 = new MyRunnable(); MyRunnable runnable2 = new MyRunnable(); new Thread(runnable1).start(); new Thread(runnable2).start(); }}
二 线程操作的一些方法
(1)线程的强制启动
在线程操作中,可以使用join()方法让一个线程强制启动。该线程强制启动期间,一直到该线程运行结束其他线程都不能运行,必须等待该线程结束之后才能继续运行
package javase.thread;class MyThread2 implements Runnable{ public void run(){ for(int i=0;i<6;i++) System.out.println("正在运行线程:" + Thread.currentThread().getName() + " --> " + i); }}public class ThreadJoinDemo { public static void main(String[] args) { MyThread2 demo = new MyThread2(); Thread thread = new Thread(demo, "线程"); thread.start(); for(int i=0;i<6;i++){ System.out.println("Main线程运行 --> " + i); if(i > 2){ try { /** * 线程强制运行 * 线程强制运行期间,其他线程无法运行,必须等待此线程完成之后才可以继续执行 * */ thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
输出:
Main线程运行 --> 0 正在运行线程:线程 --> 0 正在运行线程:线程 --> 1 正在运行线程:线程 --> 2 正在运行线程:线程 --> 3 正在运行线程:线程 --> 4 正在运行线程:线程 --> 5 Main线程运行 --> 1 Main线程运行 --> 2 Main线程运行 --> 3 Main线程运行 --> 4 Main线程运行 --> 5
注:结果不唯一
(2)中断线程
当一个线程运行时,在其他线程中可以中断该线程的运行状态。使用方法:interrupt()
package javase.thread; class MyThread3 implements Runnable{ public void run(){ System.out.println("1 run方法开始执行"); try { Thread.sleep(10000); System.out.println("2 休眠结束"); } catch (InterruptedException e) { System.out.println("3 休眠被终止"); return; } System.out.println("4 run方法正常结束"); } } public class ThreadInterruptDemo { public static void main(String[] args) { Thread thread = new Thread(new MyThread3()); thread.start(); //线程启动 try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } thread.interrupt(); //主线程暂停2s之后将其中断 } }
输出:
1 run方法开始执行 3 休眠被终止
可以看出,在主线程休眠2s之后就将线程类强制中断了,使其run方法并没有正常运行结束
(3)后台线程
前台线程结束了,后台线程仍然可以继续运行。使用方法:setDaemon(true)
package javase.thread; class MyThread4 implements Runnable{ public void run() { while(true) System.out.println(Thread.currentThread().getName() + "在运行"); } } public class ThreadDaemonDemo { public static void main(String[] args) { Thread t = new Thread(new MyThread4(),"线程"); t.setDaemon(true); //此方法需要放在start方法之前才会生效 t.start(); } }
看起来是一个不会结束的死循环,但是由于有了后台进程的缘故,程序仍然可以正常结束
(4)线程的优先级
在Java中,线程一共有3种优先级,分别是:
定义 | 描述 | 表示的常量 |
public static final int MIN_PRIORITY | 线程可以具有的最低优先级 | 1 |
public static final int NORM_PRIORITY | 分配给线程的默认优先级 | 5 |
public static final int MAX_PRIORITY | 线程可以具有的最高优先级 | 10 |
package javase.thread; class MyThread5 implements Runnable{ public void run(){ for(int i=0;i<3;i++){ try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("正在运行线程:" + Thread.currentThread().getName() + " --> " + i); } } } public class ThreadPriorityDemo { public static void main(String[] args) { System.out.println("主方法的线程优先级: " + Thread.currentThread().getPriority()); Thread t1 = new Thread(new MyThread5(), "线程1"); Thread t2 = new Thread(new MyThread5(),"线程2"); Thread t3 = new Thread(new MyThread5(),"线程3"); t1.setPriority(Thread.MIN_PRIORITY); // 1 t2.setPriority(Thread.NORM_PRIORITY); // 5 t3.setPriority(Thread.MAX_PRIORITY); // 10 t1.start(); t2.start(); t3.start(); } }
输出:
主方法的线程优先级: 5 正在运行线程:线程1 --> 0 正在运行线程:线程2 --> 0 正在运行线程:线程3 --> 0 正在运行线程:线程1 --> 1 正在运行线程:线程2 --> 1 正在运行线程:线程3 --> 1 正在运行线程:线程2 --> 2 正在运行线程:线程1 --> 2 正在运行线程:线程3 --> 2
PS:从上面的示例可以看出,主线程的优先级是5,也就是默认的优先级。题外话:Java程序每次运行至少需要启动几个线程?答案是2,一个是main(主)线程,另一个是垃圾回收线程
(5)线程的礼让
在线程操作中,可以使用yield()方法让一个线程将执行权暂时让给另一个线程
package javase.thread; class MyThread6 implements Runnable{ public void run(){ for(int i=0;i<6;i++){ System.out.println("正在运行线程:" + Thread.currentThread().getName() + " --> " + i); if(i == 3){ System.out.print(Thread.currentThread().getName() + "进行礼让: "); Thread.currentThread().yield(); } } } } public class ThreadYieldDemo { public static void main(String[] args) { MyThread6 mThread6 = new MyThread6(); Thread t1 = new Thread(mThread6, "线程A"); Thread t2 = new Thread(mThread6, "线程B"); t1.start(); t2.start(); } }
输出:
正在运行线程:线程A --> 0 正在运行线程:线程B --> 0 正在运行线程:线程A --> 1 正在运行线程:线程B --> 1 正在运行线程:线程A --> 2 正在运行线程:线程A --> 3 正在运行线程:线程B --> 2 线程A进行礼让: 正在运行线程:线程B --> 3 正在运行线程:线程A --> 4 线程B进行礼让: 正在运行线程:线程A --> 5 正在运行线程:线程B --> 4 正在运行线程:线程B --> 5
三 线程的同步与死锁
(1)问题引出:
我这里以多个线程共同卖票来举例说明。通过Runnable接口实现了多线程,然后实例化3个线程对象共同卖10张票:
package javase.thread; class MyThread7 implements Runnable{ private int ticket = 10; //多个线程共享 public void run(){ while(ticket > 0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("卖票,剩余票数: ticket = " + --ticket); } } } public class SyncDemo1 { public static void main(String[] args) { MyThread7 mThread7 = new MyThread7(); Thread t1 = new Thread(mThread7,"售票员A"); Thread t2 = new Thread(mThread7,"售票员B"); Thread t3 = new Thread(mThread7,"售票员C"); t1.start(); t2.start(); t3.start(); } }
输出:
卖票,剩余票数: ticket = 9 卖票,剩余票数: ticket = 9 卖票,剩余票数: ticket = 8 卖票,剩余票数: ticket = 7 卖票,剩余票数: ticket = 7 卖票,剩余票数: ticket = 6 卖票,剩余票数: ticket = 4 卖票,剩余票数: ticket = 5 卖票,剩余票数: ticket = 3 卖票,剩余票数: ticket = 2 卖票,剩余票数: ticket = 2 卖票,剩余票数: ticket = 1 卖票,剩余票数: ticket = 0 卖票,剩余票数: ticket = 0 卖票,剩余票数: ticket = -1
可以看出,最后的结果竟然出现了负数。这就是因为在线程的执行过程中由于没有把需要一起执行的命令部分加锁,导致了本来应该一起执行的几条命令在执行了一部分之后被另一个线程抢过了执行权,因而最后的数据出错
(2)通过添加同步代码块的方式解决:
格式:
synchronized (同步对象) {
需要同步的代码
}
package javase.thread; class MyThread8 implements Runnable{ private int ticket = 10; public void run(){ //添加同步代码块 synchronized (this) { while(ticket > 0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("卖票,剩余票数: ticket = " + --ticket); } } } } public class SyncDemo2 { public static void main(String[] args) { MyThread8 mThread8 = new MyThread8(); Thread t1 = new Thread(mThread8,"售票员A"); Thread t2 = new Thread(mThread8,"售票员B"); Thread t3 = new Thread(mThread8,"售票员C"); t1.start(); t2.start(); t3.start(); } }
输出:
卖票,剩余票数: ticket = 9 卖票,剩余票数: ticket = 8 卖票,剩余票数: ticket = 7 卖票,剩余票数: ticket = 6 卖票,剩余票数: ticket = 5 卖票,剩余票数: ticket = 4 卖票,剩余票数: ticket = 3 卖票,剩余票数: ticket = 2 卖票,剩余票数: ticket = 1 卖票,剩余票数: ticket = 0
可以看出这下结果就很正确了
(3)通过添加同步方法的方式解决:
格式:
访问权限{public|default|protected|private} synchronized 方法返回值 方法名称(参数列表){
}
package javase.thread; class MyThread9 implements Runnable { private int ticket = 10; public void run() { sale(); } /** * 同步方法 * */ public synchronized void sale() { while (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("卖票,剩余票数: ticket = " + --ticket); } } } public class SyncDemo3 { public static void main(String[] args) { MyThread9 mThread9 = new MyThread9(); Thread t1 = new Thread(mThread9, "售票员A"); Thread t2 = new Thread(mThread9, "售票员B"); Thread t3 = new Thread(mThread9, "售票员C"); t1.start(); t2.start(); t3.start(); } }
输出:略
四 线程操作经典实例——生产者与消费者
要求是:生产者生产出一个产品后消费者才能消费,否则等待,一直到生产者生产出产品后将消费者唤醒;反之亦然。关于等待和唤醒分别是这两个方法:wait()和notify()。代码如下:
package javase.thread; /** * 表示一个网站的基本信息 * */ class Info { private String name; private String url; private boolean flag = false; // 标记赋值状态,false表示还未赋值 /** * 赋值 * */ public synchronized void setValues(String name, String url) { // 如果已被赋值,等待 if (flag) { try { this.wait(); // 已经赋值而且值未被使用,则一直等待被使用 } catch (InterruptedException e) { e.printStackTrace(); } } this.name = name; try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } this.url = url; flag = true; // 赋值之后,更新标记状态 this.notify(); // 赋值之后,唤醒等待取值的线程 } /** * 取值 * */ public synchronized void getValues() { // 如果未被赋值,等待 if (!flag) { try { this.wait(); // 上一次赋的值已经使用过,下一次的还未生产 } catch (InterruptedException e) { e.printStackTrace(); } } try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("站点:" + name + " --> URL:" + url); flag = false; this.notify(); // 唤醒等待赋值的线程 } } /** * 生产者,负责给参数赋值 * */ class Producer implements Runnable { private Info info = null; public Producer(Info info) { this.info = info; } public void run() { for (int i = 0; i < 10; i++) { // 交替赋值 if (i % 2 == 0) info.setValues("zifangsky的个人博客", "http://www.zifangsky.cn"); else info.setValues("51CTO博客", "http://blog.51cto.com"); } } } /** * 消费者,负责取值 * */ class Consumer implements Runnable { private Info info = null; public Consumer(Info info) { this.info = info; } public void run() { for (int i = 0; i < 10; i++) info.getValues(); } } public class Demo { public static void main(String[] args) { Info info = new Info(); Producer producer = new Producer(info); Consumer consumer = new Consumer(info); new Thread(producer).start(); new Thread(consumer).start(); } }
输出:
站点:zifangsky的个人博客 --> URL:http://www.zifangsky.cn 站点:51CTO博客 --> URL:http://blog.51cto.com 站点:zifangsky的个人博客 --> URL:http://www.zifangsky.cn 站点:51CTO博客 --> URL:http://blog.51cto.com 站点:zifangsky的个人博客 --> URL:http://www.zifangsky.cn 站点:51CTO博客 --> URL:http://blog.51cto.com 站点:zifangsky的个人博客 --> URL:http://www.zifangsky.cn 站点:51CTO博客 --> URL:http://blog.51cto.com 站点:zifangsky的个人博客 --> URL:http://www.zifangsky.cn 站点:51CTO博客 --> URL:http://blog.51cto.com
附:另一个类似实例,关于生产电脑和卖电脑:
package javase.thread; class Computer{ private String name; private boolean status = false; //标记computer状态,false表示还未生产 private int sum = 0; public synchronized String getName() { if(!status){ //未生产,等待电脑生产 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } status = false; notify(); return name; } public synchronized void setName(String name) { if(status){ /** * 如果生产的电脑没有卖出,则要等待电脑卖出之后再生产, * 并统计出生产的电脑数量 * */ System.out.println("到目前为止,一共生产出了 " + sum + " 台电脑"); try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } this.name = name; sum++; status = true; notify(); } } /** * 生产电脑 * */ class ComputerProducer implements Runnable{ Computer computer = null; public ComputerProducer(Computer computer) { this.computer = computer; } public void run() { for(int i=0;i<10;i++) computer.setName("zifangsky00" + i); } } /** * 卖电脑 * */ class ComputerConsumer implements Runnable{ Computer computer = null; public ComputerConsumer(Computer computer) { this.computer = computer; } public void run() { for(int i=0;i<10;i++) System.out.println("卖出电脑,编号是: " + computer.getName()); } } public class Demo2 { public static void main(String[] args) { Computer computer = new Computer(); ComputerProducer producer = new ComputerProducer(computer); ComputerConsumer consumer = new ComputerConsumer(computer); new Thread(producer).start(); new Thread(consumer).start(); } }
输出:
到目前为止,一共生产出了 1 台电脑 卖出电脑,编号是: zifangsky000 到目前为止,一共生产出了 2 台电脑 卖出电脑,编号是: zifangsky001 到目前为止,一共生产出了 3 台电脑 卖出电脑,编号是: zifangsky002 到目前为止,一共生产出了 4 台电脑 卖出电脑,编号是: zifangsky003 到目前为止,一共生产出了 5 台电脑 卖出电脑,编号是: zifangsky004 到目前为止,一共生产出了 6 台电脑 卖出电脑,编号是: zifangsky005 到目前为止,一共生产出了 7 台电脑 卖出电脑,编号是: zifangsky006 到目前为止,一共生产出了 8 台电脑 卖出电脑,编号是: zifangsky007 到目前为止,一共生产出了 9 台电脑 卖出电脑,编号是: zifangsky008 卖出电脑,编号是: zifangsky009