[TOC]
线程间通信 等待/通知机制 在前面线程方法中我们遇到了wait();notify();方法。接下来我们讲讲这些方法。
wait() wait
方法的作用是将当前执行代码的线程进行等待。但wait
并不是Thread
的方法而是Object
的方法。至于为什么不放在Thread
请看这里为什么wait方法定义在Object,而不是Thread 。这篇文章讲的很清楚了。wait
方法是将当前线程放入“预执行队列”中。并且在wait
所在的代码处停止执行。直到接到通知或中断为止。同时需要注意。在调用wait
方法之前。线程需要获得该对象的对象对象级别锁。如果调用wait
时没有获取到锁则抛出异常。
wait(long) 带参数的方法表示超过这个时间就会自动唤醒。也就是等待多久。
notify()/notifyAll() notify()
;方法恰好与wait
相对。当线程使用wait
方法等待时可以使用notify()
将其唤醒。同理notify
也需要在同步方法或同步代码块中调用。notifyAll()
与notify()
的功能一样。只不过notify()
是唤醒一个线程。而notifyAll()
是唤醒所有线程。(针对同一个对象的所有线程)。
使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class ThreadTest4 { public static void main (String[] args) throws InterruptedException { Object o = new Object(); Thread1 t1 = new Thread1(o); t1.start(); Thread.sleep(100 ); synchronized (o){ System.out.println("开始解锁" ); o.notifyAll(); System.out.println("结束解锁" ); } } } class Thread1 extends Thread { private Object lock; Thread1(Object lock) { this .lock = lock; } @Override public void run () { try { synchronized (lock) { System.out.println("wait开始" ); lock.wait(); System.out.println("wait结束:" ); } } catch (InterruptedException e) { e.printStackTrace(); } } }
在线程run之后进入等待。同时主线程休眠0.5秒。0.5秒之后调用notify
唤醒线程。所以在调用wait方法之后wait
后面的代码就会停止执行。直到notify
执行完毕。当唤醒之后wai
t后面的代码继续执行线程之间的状态切换 每个对象都有两个队列。一个是阻塞队列,另一个是就绪队列。当线程被唤醒之后会进入就绪队列。反之则进入阻塞队列。
wait()/notify()/锁 当wait
执行完后。锁会自动释放。但是执行完notify
却不会自动释放锁。从之前的代码中我们知道。代码运行到wait
的时候会进行等待。所以wait
后面的代码就不执行了。刚开始的时候我以为代码停在这了。所以锁也没释放。其实当wait
运行完之后锁就会自动释放。将上面的代码稍加改动:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 public class ThreadTest4 { public static void main (String[] args) throws InterruptedException { Object o = new Object(); Thread1 t1 = new Thread1(o); Thread1 t2 = new Thread1(o); t1.start(); t2.start(); Thread.sleep(100 ); synchronized (o){ System.out.println("开始解锁" ); o.notifyAll(); System.out.println("结束解锁" ); } } } class Thread1 extends Thread { private Object lock; Thread1(Object lock) { this .lock = lock; } @Override public void run () { try { synchronized (lock) { System.out.println("wait开始" ); lock.wait(); System.out.println("wait结束:" ); } } catch (InterruptedException e) { e.printStackTrace(); } } }
通过运行的结果表明,在调用wait
之后锁就已经被释放掉了。所有线程都要等待notify
的同步代码块执行完毕才会继续执行
生产者/消费者模式实现 等待/通知最经典的案例就是“生产者/消费者”模式。
一生产与一消费 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 class P { private String lock; P(String lock) { this .lock = lock; } public void setVale () { try { synchronized (lock) { if (!"" .equals(ThreadTestCP.value)) { lock.wait(); } ThreadTestCP.value = System.currentTimeMillis() + "" ; System.out.println("生产者:" + ThreadTestCP.value); lock.notify(); } } catch (Exception ignored) { } } } class C { private String lock; C(String lock) { this .lock = lock; } public void getValue () { try { synchronized (lock) { if ("" .equals(ThreadTestCP.value)) { lock.wait(); } System.out.println("消费者:" + ThreadTestCP.value); ThreadTestCP.value = "" ; lock.notify(); } } catch (Exception ignored) { } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class ThreadTestCP { public static String value; public static void main (String[] args) { String lock="aaa" ; new Thread(()->{ P p = new P(lock); while (true ){ p.setValue(); } }).start(); new Thread(()->{ C p = new C(lock); while (true ){ p.setValue(); } }).start(); } }
每次生产者生产完成之后唤醒等待线程。然后消费者进行消费。在这里如果出现多个消费者或多个生产者。那么线程就有可能出现假死状态。也就是所有线程都处于等待状态。可以将notify
换成notifyAll
解决假死状态。 同时这里的notify
是没有区分生产者/消费者的。也就是说生产者唤醒的也有可能是生产者。简单来说就是 : 消费者查看是否有数据。如果没有就等待。如果有就消费,随后唤醒生产者进行生产。 生产者被唤醒之后进行生产。生产之后唤醒消费者。数据载体可以是任何对象。只要生产者和消费者的数据载体是同一对象。
实战 题目要求: 创建二十个线程。线程分为两种,一种输出“★“,另一种输出”☆“ 两种线程交替运行。 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 public class ThreadReplaceRun { public static void main (String[] args) throws InterruptedException { Tools tools = new Tools(); for (int i = 0 ; i < 20 ; i++) { new Thread(()->{ tools.MethodA(); }).start(); new Thread(()->{ tools.MethodB(); }).start(); } } } class Tools { private boolean flag = false ; public void MethodA () { synchronized (this ) { try { while (flag) { wait(); } System.out.print("★\t" ); flag = !flag; notify(); } catch (Exception ignored) { } } } public void MethodB () { synchronized (this ) { try { while (!flag) { wait(); } System.out.print("☆\t" ); flag = !flag; notify(); } catch (Exception ignored) { } } } }
通过一个Boolean变量使两个线程变成互斥运行。使其其中一个线程进入睡眠。
join方法 很多时候主线程创建并启动子线程,如果子线程中有很多耗时的操作。往往主线程比子线程更早结束。如果在主线程中需要使用到子线程中的数据,这个时候就需要join
方法了。 join方法是使所属线程正常运行run方法。当前线程无限制等待。直到所属线程销毁。
1 2 3 4 5 6 7 8 9 10 11 12 13 public class ThreadReplaceRun { public static void main (String agrs) { Thread t=new Thread(()->{ try { Thread.sleep(500 ); System.out.println("子线程" ); }catch (InterruptedException e){} }); t.start(); t.join(); System.out.println("主线程" ); } }
在执行过程中,子线程会休眠一段时间,在休眠完成之后主线程开始执行。其实join方法的底层也只是使用了wait方法;只是更方便我们使用。注意 join方法不能与interrupt方法一起使用。
join(long)与sleep(long)的区别 这两者在运行的效果上基本没什么区别。只是咋对待同步的处理上有点区别。由于join底层是使用wait进行等待的,所以具有释放锁的特点。
ThreadLocal 在前面我们都是使用的public static
变量的形式。所有线程都使用同一个public static
变量。 而ThreadLocal
则是为每一个线程提供共享变量的功能。ThreadLocal
可以被视为给线程存放数据的箱子
。
ThreadLocal线程隔离 我们可以使用一个ThreadLocal类为每一个线程存入数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class ThreadJoinAndSleep { public static ThreadLocal tl = new ThreadLocal(); public static void main (String[] args) throws InterruptedException { for (int i = 0 ; i < 5 ; i++) { Thread t = new Thread(() -> { tl.set(Thread.currentThread().getName() + "" ); System.out.println(Thread.currentThread().getName()+":" +tl.get()); }); t.setName("" + i); t.start(); } System.out.println(Thread.currentThread().getName()+":" +tl.get()); } }
不难发现,每个线程get的都只是那个线程存入的数据。不能获取其他线程的数据。
get为null 在没有进行set之前任何一个线程get返回的都是null。如果想设置一个默认值。我们可以继承ThreadLocal,并实现inittialValue()
方法返回默认值