使线程之间进行通信之后,系统间的交互性更加强大,在大大提高CPU利用率的同时还会使程序对各线程任务在处理的过程中进行有效的把控与监督。
1.不使用wait/notify实现线程间通信
使用sleep()+while(true)也可以实现线程间通信。
例如:两个线程,一个线程向集合中添加元素,当集合中元素大小等于5的时候停止第二个线程
package cn.qlq.thread.six;import java.util.ArrayList; import java.util.List;import org.slf4j.Logger; import org.slf4j.LoggerFactory;/*** sleep()结合while(true)实现多个线程间通信* * @author QiaoLiQiang* @time 2018年12月10日下午9:32:52*/ public class Demo1 {private static final Logger LOGGER = LoggerFactory.getLogger(Demo1.class);private static volatile List list = new ArrayList<>();// 加volatile关键字是为了每次从主存中读取数据,否则两个线程读取到的不一致public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic void run() {try {for (int i = 0; i < 10; i++) {LOGGER.debug("add ele ->{},threadName->{}", i, Thread.currentThread().getName());list.add(String.valueOf(i));Thread.sleep(1 * 1000);}} catch (InterruptedException e) {LOGGER.error("InterruptedException", e);}}}, "A").start();new Thread(new Runnable() {@Overridepublic void run() {try {while (true) {if (list.size() == 5) {LOGGER.debug("list大小为5 了,线程B要退出了");throw new InterruptedException();}}} catch (InterruptedException e) {LOGGER.error("抛出异常,线程退出", e);}}}, "B").start();} }
结果:
21:45:05 [cn.qlq.thread.six.Demo1]-[DEBUG] add ele ->0,threadName->A
21:45:06 [cn.qlq.thread.six.Demo1]-[DEBUG] add ele ->1,threadName->A
21:45:07 [cn.qlq.thread.six.Demo1]-[DEBUG] add ele ->2,threadName->A
21:45:08 [cn.qlq.thread.six.Demo1]-[DEBUG] add ele ->3,threadName->A
21:45:09 [cn.qlq.thread.six.Demo1]-[DEBUG] add ele ->4,threadName->A
21:45:09 [cn.qlq.thread.six.Demo1]-[DEBUG] list大小为5 了,线程B要退出了
21:45:09 [cn.qlq.thread.six.Demo1]-[ERROR] 抛出异常,线程退出
java.lang.InterruptedException
at cn.qlq.thread.six.Demo1$2.run(Demo1.java:43)
at java.lang.Thread.run(Thread.java:745)
21:45:10 [cn.qlq.thread.six.Demo1]-[DEBUG] add ele ->5,threadName->A
21:45:11 [cn.qlq.thread.six.Demo1]-[DEBUG] add ele ->6,threadName->A
21:45:12 [cn.qlq.thread.six.Demo1]-[DEBUG] add ele ->7,threadName->A
21:45:13 [cn.qlq.thread.six.Demo1]-[DEBUG] add ele ->8,threadName->A
21:45:14 [cn.qlq.thread.six.Demo1]-[DEBUG] add ele ->9,threadName->A
虽然两个线程间实现了通信,但有一个弊端就是,线程B不停地通过while语句轮询机制来检测某一个条件,这样会浪费CPU资源。
如果轮询的时间间隔很小,更浪费CPU资源;如果轮询的时间间隔很大,有可能取不到想得到的数据。所以需要一种机制来实现减少CPU的资源,而且还可以实现在多个线程间通信,它就是"wait/notify"机制。
2. 等待/通知的实现
等待/通知在生活中的例子非常多,比如在就餐时的一个例子:比如厨师炒好菜之后通知服务员上菜就是一个典型的等待通知的实现。
在前面几篇博客中多个线程也可以实现线程间通信,原因就是多个线程共同访问同一个变量,但那种通信机制不是"等待/通知",两个线程完全是主动地读取一个共享变量,在花费读取时间的基础上,读到的值并不是想要的,并不能完全确定。所以"等待/通知"可以完美解决上述的问题。
方法wait()的作用是使当前执行代码的线程进行等待,wait()是object类的方法,该方法用来将当前线程置入"预执行队列",并且在wait()所在的代码行处进行停止,直到接到通知或者被中断为止。在调用wait()之前,线程必须获得该对象的对象级别锁,即只能在同步方法或者同步代码块中调用wait()方法。在执行wait()方法后,当前线程释放锁。在从wait()返回前,线程与其他线程竞争重新获得锁。如果调用wait()时没有持有适当的锁则会抛出IllegalMonitorStateException,它是RuntimeException的一个子类,因此,不需要try-catch语句进行捕获异常。
方法notify()也要在同步方法或者同步块中调用,即调用前也必须获得该对象的对象级别锁。如果调用notify()时没有持有适当的锁则会抛出IllegalMonitorStateException。该方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程,则有线程规划器随机挑选其中一个呈wait状态的线程,对其发出notify,并使它等待获取该对象的对象锁。需要说明的是,在执行notify()之后,当前线程不会马上释放该对象锁,呈wait状态的线程也不会马上获得该对象锁,要等到nitofy()方法的线程将程序执行完,也就是退出synchronized同步方法或者代码块释放锁之后。当第一个对象获得了该对象锁的wait线程继续运行完毕以后,它会释放掉对象锁,此时如果该对象没有再次使用notify()语句,即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,还会继续阻塞在wait状态,直到这个对象发出一个notify或notifyAll。
一句话总结:wait使线程停止运行,释放锁,而notify使线程继续运行,但不会释放锁。两者都必须在获得同步锁才能使用。
如下:没有同步锁中调用wait()方法报错:(出现异常的原因是没有"对象监视器",也就是没有同步加锁)
package cn.qlq.thread.six;/*** 不在同步中调用wait* * @author Administrator**/ public class Demo2 {public static void main(String[] args) throws InterruptedException {Object object = new Object();object.wait();} }
结果:
Exception in thread "main" java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:503)
at cn.qlq.thread.six.Demo2.main(Demo2.java:6)
一个简单的wait/notify的例子:
package cn.qlq.thread.six;import org.slf4j.Logger; import org.slf4j.LoggerFactory;/*** wait/notify的基本使用* * @author Administrator**/ public class Demo3 {private static final Logger LOGGER = LoggerFactory.getLogger(Demo3.class);public static void main(String[] args) throws InterruptedException {final Object object = new Object();Thread threadA = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {try {LOGGER.debug("进入同步代码块,准备wait(),threadName->{}", Thread.currentThread().getName());Thread.sleep(1 * 1000);object.wait();LOGGER.debug("退出同步代码块,wait()结束,threadName->{}", Thread.currentThread().getName());} catch (InterruptedException e) {LOGGER.error("InterruptedException error");}}}}, "A");threadA.start();// 开启一个线程占用锁之后唤醒一个线程Thread.sleep(1);Thread threadB = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {try {LOGGER.debug("进入同步代码块,准备wait(),threadName->{}", Thread.currentThread().getName());Thread.sleep(1 * 1000);object.wait();LOGGER.debug("退出同步代码块,wait()结束,threadName->{}", Thread.currentThread().getName());} catch (InterruptedException e) {LOGGER.error("InterruptedException error");}}}}, "B");threadB.start();// 开启一个线程占用锁之后唤醒一个线程Thread.sleep(1);Thread threadC = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {try {LOGGER.debug("进入同步代码块,准备notify(),threadName->{}", Thread.currentThread().getName());Thread.sleep(1 * 1000);object.notify();// object.notifyAll();LOGGER.debug("退出同步代码块,notify()结束,threadName->{}", Thread.currentThread().getName());} catch (InterruptedException e) {e.printStackTrace();}}}}, "C");threadC.start();Thread.sleep(5 * 1000);LOGGER.debug("A线程状态->{}", threadA.getState());LOGGER.debug("B线程状态->{}", threadB.getState());LOGGER.debug("C线程状态->{}", threadC.getState());} }
结果:(A线程首先进入同步代码块,然后通过wait之后释放锁;C占用锁,然后发出notify通知;C执行完之后B获得同步锁(B是正常的获得锁),然后进入wait释放锁;接下来A获得锁之后执行wait后面的代码,执行完释放锁,但是由于没有notify所以B仍然处于waiting状态。)--也证明发出notify()不会释放锁,而是方法正常结束后才释放锁。
采用wait/notify实现上面的例子:两个线程,一个线程向集合中添加元素,当集合中元素大小等于5的时候停止第二个线程
package cn.qlq.thread.six;import java.util.ArrayList; import java.util.List;import org.slf4j.Logger; import org.slf4j.LoggerFactory;/*** wait/notify的实现两个线程通信* * @author Administrator**/ public class Demo4 {private static final Logger LOGGER = LoggerFactory.getLogger(Demo4.class);public static void main(String[] args) throws InterruptedException {final List<String> list = new ArrayList<String>();Thread threadA = new Thread(new Runnable() {@Overridepublic void run() {synchronized (list) {try {for (int i = 0; i < 10; i++) {list.add(i + "");LOGGER.debug("add ele -> {}", i);if (list.size() == 5) {LOGGER.debug("wait---------------");list.wait();}}} catch (InterruptedException e) {LOGGER.error("InterruptedException error");}}}}, "A");threadA.start();// 开启一个线程占用锁之后唤醒一个线程Thread.sleep(1);Thread threadB = new Thread(new Runnable() {@Overridepublic void run() {synchronized (list) {LOGGER.debug("run,threadName->{}", Thread.currentThread().getName());list.notify();LOGGER.debug("threadName->{}, notify", Thread.currentThread().getName());}}}, "B");threadB.start();} }
结果:
18:05:23 [cn.qlq.thread.six.Demo4]-[DEBUG] add ele -> 0
18:05:23 [cn.qlq.thread.six.Demo4]-[DEBUG] add ele -> 1
18:05:23 [cn.qlq.thread.six.Demo4]-[DEBUG] add ele -> 2
18:05:23 [cn.qlq.thread.six.Demo4]-[DEBUG] add ele -> 3
18:05:23 [cn.qlq.thread.six.Demo4]-[DEBUG] add ele -> 4
18:05:23 [cn.qlq.thread.six.Demo4]-[DEBUG] wait---------------
18:05:23 [cn.qlq.thread.six.Demo4]-[DEBUG] run,threadName->B
18:05:23 [cn.qlq.thread.six.Demo4]-[DEBUG] threadName->B, notify
18:05:23 [cn.qlq.thread.six.Demo4]-[DEBUG] add ele -> 5
18:05:23 [cn.qlq.thread.six.Demo4]-[DEBUG] add ele -> 6
18:05:23 [cn.qlq.thread.six.Demo4]-[DEBUG] add ele -> 7
18:05:23 [cn.qlq.thread.six.Demo4]-[DEBUG] add ele -> 8
18:05:23 [cn.qlq.thread.six.Demo4]-[DEBUG] add ele -> 9
关键字Synchronized可以将一个Object对象作为同步对象来看待,而Java为每个Object都实现了wait()和notify()方法,它们必须用在被synchronized同步的object的临界区内。通过调用wait()方法可以使处于临界区的线程进入等待状态,同时释放同步对象的锁。而notify()操作可以唤醒一个因调用了wait操作而处于线程阻塞状态中的线程,使其进入就绪状态。被重新唤醒的线程会试图重新获取临界区的控制权,也就 是锁,并继续执行临界区内wait之后的代码。如果发出notify操作时没有处于线程阻塞状态中的线程,那么该命令会被忽略。
wait()方法可以使调用该方法的线程释放共享资源的锁,然后从运行状态退出,进入等待队列,直到被再次唤醒。
notify()方法可以随机唤醒等待队列中等待同一共享资源的"一个"线程,并使该线程退出等待队列,进入可运行状态,也就是notify()仅通知"一个"线程。
notifyAll()方法可以使所有正在等待队列中等待同一个共享资源的"全部"线程从等待状态退出,进入可运行状态。此时,优先级最高的那个线程最先执行,但是也有可能是随机执行,这取决于JVM虚拟机的实现。
每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列。就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程。一个线程被唤醒后,才会进入就绪队列,等待CPU的调度;反之,一个线程被wait之后就进入阻塞队列,等待下一次被唤醒。
3. 当interrupt方法遇到wait方法
当线程处于wait()状态时,调用线程对象的interrupt()方法会出现InterruptedException对象。
package cn.qlq.thread.six;import java.util.ArrayList; import java.util.List;import org.slf4j.Logger; import org.slf4j.LoggerFactory;/*** wait遇到interrupt* * @author Administrator**/ public class Demo5 {private static final Logger LOGGER = LoggerFactory.getLogger(Demo5.class);public static void main(String[] args) throws InterruptedException {final List<String> list = new ArrayList<String>();Thread threadA = new Thread(new Runnable() {@Overridepublic void run() {synchronized (list) {try {for (int i = 0; i < 10; i++) {list.add(i + "");LOGGER.debug("add ele -> {}", i);if (list.size() == 5) {LOGGER.debug("wait---------------");list.wait();}}} catch (InterruptedException e) {LOGGER.error("InterruptedException error", e);}}}}, "A");threadA.start();// 睡眠两秒钟发出中断信号Thread.sleep(2 * 1000);threadA.interrupt();} }
结果:
21:35:00 [cn.qlq.thread.six.Demo5]-[DEBUG] add ele -> 0
21:35:00 [cn.qlq.thread.six.Demo5]-[DEBUG] add ele -> 1
21:35:00 [cn.qlq.thread.six.Demo5]-[DEBUG] add ele -> 2
21:35:00 [cn.qlq.thread.six.Demo5]-[DEBUG] add ele -> 3
21:35:00 [cn.qlq.thread.six.Demo5]-[DEBUG] add ele -> 4
21:35:00 [cn.qlq.thread.six.Demo5]-[DEBUG] wait---------------
21:35:02 [cn.qlq.thread.six.Demo5]-[ERROR] InterruptedException error
java.lang.InterruptedException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:503)
at cn.qlq.thread.six.Demo5$1.run(Demo5.java:30)
at java.lang.Thread.run(Thread.java:745)
4. 只唤醒一个线程
调用notify()一次只随机唤醒一个线程,多次调用可以唤醒多个,注意唤醒的是阻塞队列中被阻塞的线程。
package cn.qlq.thread.six;import org.slf4j.Logger; import org.slf4j.LoggerFactory;/*** notify()每次唤醒一个线程* * @author Administrator**/ public class Demo6 {private static final Logger LOGGER = LoggerFactory.getLogger(Demo6.class);public static void main(String[] args) throws InterruptedException {final Object object = new Object();Thread.sleep(1 * 1000);Thread threadA = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {try {Thread.sleep(2 * 1000);LOGGER.info("wait start---------------,threadName->{}", Thread.currentThread().getName());object.wait();LOGGER.info("wait end---------------,threadName->{}", Thread.currentThread().getName());} catch (InterruptedException e) {LOGGER.error("InterruptedException error", e);}}}}, "A");threadA.start();Thread.sleep(1 * 1000);Thread threadB = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {try {Thread.sleep(2 * 1000);LOGGER.info("wait start---------------,threadName->{}", Thread.currentThread().getName());object.wait();LOGGER.info("wait end---------------,threadName->{}", Thread.currentThread().getName());} catch (InterruptedException e) {LOGGER.error("InterruptedException error", e);}}}}, "B");threadB.start();Thread.sleep(1 * 1000);Thread threadC = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {try {Thread.sleep(2 * 1000);LOGGER.info("wait start---------------,threadName->{}", Thread.currentThread().getName());object.wait();LOGGER.info("wait end---------------,threadName->{}", Thread.currentThread().getName());} catch (InterruptedException e) {LOGGER.error("InterruptedException error", e);}}}}, "C");threadC.start();Thread.sleep(1 * 1000);Thread threadD = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {try {Thread.sleep(2 * 1000);LOGGER.info("notify start---------------,threadName->{}", Thread.currentThread().getName());object.notify();LOGGER.info("notify end---------------,threadName->{}", Thread.currentThread().getName());} catch (InterruptedException e) {LOGGER.error("InterruptedException error", e);}}}}, "D");threadD.start();Thread.sleep(10 * 1000);LOGGER.info("threadA state->{}", threadA.getState());LOGGER.info("threadB state->{}", threadB.getState());LOGGER.info("threadC state->{}", threadC.getState());LOGGER.info("threadD state->{}", threadD.getState());} }
结果:(可以看到前面阻塞了ABC线程,D线程随机唤醒了一个线程A)
21:53:16 [cn.qlq.thread.six.Demo6]-[INFO] wait start---------------,threadName->A
21:53:18 [cn.qlq.thread.six.Demo6]-[INFO] wait start---------------,threadName->C
21:53:20 [cn.qlq.thread.six.Demo6]-[INFO] wait start---------------,threadName->B
21:53:22 [cn.qlq.thread.six.Demo6]-[INFO] notify start---------------,threadName->D
21:53:22 [cn.qlq.thread.six.Demo6]-[INFO] notify end---------------,threadName->D
21:53:22 [cn.qlq.thread.six.Demo6]-[INFO] wait end---------------,threadName->A
21:53:27 [cn.qlq.thread.six.Demo6]-[INFO] threadA state->TERMINATED
21:53:27 [cn.qlq.thread.six.Demo6]-[INFO] threadB state->WAITING
21:53:27 [cn.qlq.thread.six.Demo6]-[INFO] threadC state->WAITING
21:53:27 [cn.qlq.thread.six.Demo6]-[INFO] threadD state->TERMINATED
修改上面D线程的代码多次调用notify()可以看到会唤醒多个线程
Thread threadD = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {try {Thread.sleep(2 * 1000);LOGGER.info("notify start---------------,threadName->{}", Thread.currentThread().getName());object.notify();LOGGER.info("notify end---------------,threadName->{}", Thread.currentThread().getName());LOGGER.info("notify start---------------,threadName->{}", Thread.currentThread().getName());object.notify();LOGGER.info("notify end---------------,threadName->{}", Thread.currentThread().getName());LOGGER.info("notify start---------------,threadName->{}", Thread.currentThread().getName());object.notify();LOGGER.info("notify end---------------,threadName->{}", Thread.currentThread().getName());} catch (InterruptedException e) {LOGGER.error("InterruptedException error", e);}}}}, "D");threadD.start();
结果:(前面阻塞了3个线程,调用了3次notify()就唤醒了三个线程)
21:56:11 [cn.qlq.thread.six.Demo6]-[INFO] wait start---------------,threadName->A
21:56:13 [cn.qlq.thread.six.Demo6]-[INFO] wait start---------------,threadName->C
21:56:15 [cn.qlq.thread.six.Demo6]-[INFO] wait start---------------,threadName->B
21:56:17 [cn.qlq.thread.six.Demo6]-[INFO] notify start---------------,threadName->D
21:56:17 [cn.qlq.thread.six.Demo6]-[INFO] notify end---------------,threadName->D
21:56:17 [cn.qlq.thread.six.Demo6]-[INFO] notify start---------------,threadName->D
21:56:17 [cn.qlq.thread.six.Demo6]-[INFO] notify end---------------,threadName->D
21:56:17 [cn.qlq.thread.six.Demo6]-[INFO] notify start---------------,threadName->D
21:56:17 [cn.qlq.thread.six.Demo6]-[INFO] notify end---------------,threadName->D
21:56:17 [cn.qlq.thread.six.Demo6]-[INFO] wait end---------------,threadName->A
21:56:17 [cn.qlq.thread.six.Demo6]-[INFO] wait end---------------,threadName->B
21:56:17 [cn.qlq.thread.six.Demo6]-[INFO] wait end---------------,threadName->C
21:56:22 [cn.qlq.thread.six.Demo6]-[INFO] threadA state->TERMINATED
21:56:22 [cn.qlq.thread.six.Demo6]-[INFO] threadB state->TERMINATED
21:56:22 [cn.qlq.thread.six.Demo6]-[INFO] threadC state->TERMINATED
21:56:22 [cn.qlq.thread.six.Demo6]-[INFO] threadD state->TERMINATED
5. 唤醒所有的线程
notifyAll()可以唤醒对象阻塞队列中所有的线程,知识唤醒的线程重新抢占锁的时候又是随机的抢占锁,与notify()的区别是如果对象有多个阻塞线程需要多次调研notify()才能全部唤醒,而调用notifyAll()一次可以唤醒全部阻塞队列的线程。
package cn.qlq.thread.six;import org.slf4j.Logger; import org.slf4j.LoggerFactory;/*** notifyAll()唤醒全部线程* * @author Administrator**/ public class Demo7 {private static final Logger LOGGER = LoggerFactory.getLogger(Demo7.class);public static void main(String[] args) throws InterruptedException {final Object object = new Object();Thread.sleep(1 * 1000);Thread threadA = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {try {Thread.sleep(2 * 1000);LOGGER.info("wait start---------------,threadName->{}", Thread.currentThread().getName());object.wait();LOGGER.info("wait end---------------,threadName->{}", Thread.currentThread().getName());} catch (InterruptedException e) {LOGGER.error("InterruptedException error", e);}}}}, "A");threadA.start();Thread.sleep(1 * 1000);Thread threadB = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {try {Thread.sleep(2 * 1000);LOGGER.info("wait start---------------,threadName->{}", Thread.currentThread().getName());object.wait();LOGGER.info("wait end---------------,threadName->{}", Thread.currentThread().getName());} catch (InterruptedException e) {LOGGER.error("InterruptedException error", e);}}}}, "B");threadB.start();Thread.sleep(1 * 1000);Thread threadC = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {try {Thread.sleep(2 * 1000);LOGGER.info("wait start---------------,threadName->{}", Thread.currentThread().getName());object.wait();LOGGER.info("wait end---------------,threadName->{}", Thread.currentThread().getName());} catch (InterruptedException e) {LOGGER.error("InterruptedException error", e);}}}}, "C");threadC.start();Thread.sleep(1 * 1000);Thread threadD = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {try {Thread.sleep(2 * 1000);LOGGER.info("notifyAll start---------------,threadName->{}", Thread.currentThread().getName());object.notifyAll();LOGGER.info("notifyAll end---------------,threadName->{}", Thread.currentThread().getName());} catch (InterruptedException e) {LOGGER.error("InterruptedException error", e);}}}}, "D");threadD.start();Thread.sleep(10 * 1000);LOGGER.info("threadA state->{}", threadA.getState());LOGGER.info("threadB state->{}", threadB.getState());LOGGER.info("threadC state->{}", threadC.getState());LOGGER.info("threadD state->{}", threadD.getState());} }
结果:
22:02:20 [cn.qlq.thread.six.Demo7]-[INFO] wait start---------------,threadName->A
22:02:22 [cn.qlq.thread.six.Demo7]-[INFO] wait start---------------,threadName->C
22:02:24 [cn.qlq.thread.six.Demo7]-[INFO] wait start---------------,threadName->B
22:02:26 [cn.qlq.thread.six.Demo7]-[INFO] notifyAll start---------------,threadName->D
22:02:26 [cn.qlq.thread.six.Demo7]-[INFO] notifyAll end---------------,threadName->D
22:02:26 [cn.qlq.thread.six.Demo7]-[INFO] wait end---------------,threadName->B
22:02:26 [cn.qlq.thread.six.Demo7]-[INFO] wait end---------------,threadName->C
22:02:26 [cn.qlq.thread.six.Demo7]-[INFO] wait end---------------,threadName->A
22:02:31 [cn.qlq.thread.six.Demo7]-[INFO] threadA state->TERMINATED
22:02:31 [cn.qlq.thread.six.Demo7]-[INFO] threadB state->TERMINATED
22:02:31 [cn.qlq.thread.six.Demo7]-[INFO] threadC state->TERMINATED
22:02:31 [cn.qlq.thread.six.Demo7]-[INFO] threadD state->TERMINATED
6.方法 wait(long)使用---超过long时间自动唤醒
带一个参数的wait(long)方法的功能是等待某一时间内是否有线程对锁进行唤醒,如果超过这个时间则自动唤醒。
package cn.qlq.thread.six;import org.slf4j.Logger; import org.slf4j.LoggerFactory;/*** wait(long)自动唤醒* * @author Administrator**/ public class Demo8 {private static final Logger LOGGER = LoggerFactory.getLogger(Demo8.class);public static void main(String[] args) throws InterruptedException {final Object object = new Object();Thread threadA = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {try {Thread.sleep(5 * 1000);LOGGER.info("wait start---------------,threadName->{}", Thread.currentThread().getName());// 等待10秒钟自动唤醒object.wait(5 * 1000);LOGGER.info("wait end---------------,threadName->{}", Thread.currentThread().getName());Thread.sleep(5 * 1000);} catch (InterruptedException e) {LOGGER.error("InterruptedException error", e);}}}}, "A");threadA.start();Thread.sleep(2 * 1000);LOGGER.info("threadA state->{}", threadA.getState());Thread.sleep(5 * 1000);LOGGER.info("threadA state->{}", threadA.getState());Thread.sleep(5 * 1000);LOGGER.info("threadA state->{}", threadA.getState());Thread.sleep(5 * 1000);LOGGER.info("threadA state->{}", threadA.getState());} }
结果: (线程调用sleep(long)和obj.wait(long)都是处于超时等待状态)
22:06:49 [cn.qlq.thread.six.Demo8]-[INFO] threadA state->TIMED_WAITING
22:06:52 [cn.qlq.thread.six.Demo8]-[INFO] wait start---------------,threadName->A
22:06:54 [cn.qlq.thread.six.Demo8]-[INFO] threadA state->TIMED_WAITING
22:06:57 [cn.qlq.thread.six.Demo8]-[INFO] wait end---------------,threadName->A
22:06:59 [cn.qlq.thread.six.Demo8]-[INFO] threadA state->TIMED_WAITING
22:07:05 [cn.qlq.thread.six.Demo8]-[INFO] threadA state->TERMINATED
7. 通知过早
如果通知过早,则会打乱程序正常的运行逻辑。
先发出通知,此时对象的阻塞队列还没有阻塞的线程,所以相当于没有唤醒队列,导致之后阻塞的线程一直处于阻塞状态
package cn.qlq.thread.six;import org.slf4j.Logger; import org.slf4j.LoggerFactory;/*** 通知过早打乱程序的运行逻辑* * @author Administrator**/ public class Demo9 {private static final Logger LOGGER = LoggerFactory.getLogger(Demo9.class);public static void main(String[] args) throws InterruptedException {final Object object = new Object();Thread threadA = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {LOGGER.info("notify start---------------,threadName->{}", Thread.currentThread().getName());// 等待10秒钟自动唤醒 object.notify();LOGGER.info("notify end---------------,threadName->{}", Thread.currentThread().getName());}}}, "A");threadA.start();Thread.sleep(1 * 1000);Thread threadB = new Thread(new Runnable() {@Overridepublic void run() {synchronized (object) {try {LOGGER.info("wait start---------------,threadName->{}", Thread.currentThread().getName());// 等待10秒钟自动唤醒 object.wait();LOGGER.info("wait end---------------,threadName->{}", Thread.currentThread().getName());} catch (InterruptedException e) {LOGGER.error("InterruptedException error", e);}}}}, "B");threadB.start();} }
结果:(B线程会一直处于阻塞状态)
8. 等待wait的条件发生变化
在使用wait/notify的时候,还需要注意另外一种情况,也就是wait等待的条件发生了变化也容易造成程序的混乱。
package cn.qlq.thread.six;import java.util.ArrayList; import java.util.List;import org.slf4j.Logger; import org.slf4j.LoggerFactory;/*** wait条件发生变化导致程序混乱 (一个线程向集合中添加元素,两个线程从集合中删除元素)* * @author Administrator**/ public class Demo10 {private static final Logger LOGGER = LoggerFactory.getLogger(Demo10.class);public static void main(String[] args) throws InterruptedException {final List<String> list = new ArrayList<String>();// 删除元素线程1Thread sub1 = new Thread(new Runnable() {@Overridepublic void run() {try {synchronized (list) {if (list.size() == 0) {LOGGER.info("wait start---------------,threadName->{}", Thread.currentThread().getName());// 等待10秒钟自动唤醒 list.wait();LOGGER.info("wait end---------------,threadName->{}", Thread.currentThread().getName());}LOGGER.info("list.remove ->{}, threadName->{}", list.get(0), Thread.currentThread().getName());list.remove(0);}} catch (InterruptedException e) {e.printStackTrace();}}}, "sub1");sub1.start();// 删除元素线程2Thread sub2 = new Thread(new Runnable() {@Overridepublic void run() {try {synchronized (list) {if (list.size() == 0) {LOGGER.info("wait start---------------,threadName->{}", Thread.currentThread().getName());// 等待10秒钟自动唤醒 list.wait();LOGGER.info("wait end---------------,threadName->{}", Thread.currentThread().getName());}LOGGER.info("list.remove ->{}, threadName->{}", list.get(0), Thread.currentThread().getName());list.remove(0);}} catch (InterruptedException e) {e.printStackTrace();}}}, "sub2");sub2.start();// 增加元素线程Thread.sleep(1 * 1000);Thread addThread = new Thread(new Runnable() {@Overridepublic void run() {synchronized (list) {list.add("1");LOGGER.info("添加元素->{},threadName->{}", "1", Thread.currentThread().getName());list.notifyAll();LOGGER.info("threadName->{} 执行list.notifyAll() 发出通知唤醒所有阻塞线程", Thread.currentThread().getName());}}}, "B");addThread.start();} }
结果:
22:34:58 [cn.qlq.thread.six.Demo10]-[INFO] wait start---------------,threadName->sub1
22:34:58 [cn.qlq.thread.six.Demo10]-[INFO] wait start---------------,threadName->sub2
22:34:59 [cn.qlq.thread.six.Demo10]-[INFO] 添加元素->1,threadName->B
22:34:59 [cn.qlq.thread.six.Demo10]-[INFO] threadName->B 执行list.notifyAll() 发出通知唤醒所有阻塞线程
22:34:59 [cn.qlq.thread.six.Demo10]-[INFO] wait end---------------,threadName->sub2
22:34:59 [cn.qlq.thread.six.Demo10]-[INFO] list.remove ->1, threadName->sub2
22:34:59 [cn.qlq.thread.six.Demo10]-[INFO] wait end---------------,threadName->sub1
Exception in thread "sub1" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.rangeCheck(ArrayList.java:635)
at java.util.ArrayList.get(ArrayList.java:411)
at cn.qlq.thread.six.Demo10$1.run(Demo10.java:34)
at java.lang.Thread.run(Thread.java:745)
报错原因如下:首先线程开始之后两个删除元素的线程处于阻塞状态,而生产线程生产了一个元素之后就唤醒所有线程,所以上面两个线程都被唤醒,唤醒之后执行删除元素操作,第一个线程能正常删除,第二个线程删除会报越界异常的错误。
解决办法如下:
package cn.qlq.thread.six;import java.util.ArrayList; import java.util.List;import org.slf4j.Logger; import org.slf4j.LoggerFactory;/*** wait条件发生变化导致程序混乱 (一个线程向集合中添加元素,两个线程从集合中删除元素)* * @author Administrator**/ public class Demo10 {private static final Logger LOGGER = LoggerFactory.getLogger(Demo10.class);public static void main(String[] args) throws InterruptedException {final List<String> list = new ArrayList<String>();// 删除元素线程1Thread sub1 = new Thread(new Runnable() {@Overridepublic void run() {try {synchronized (list) {while (list.size() == 0) {LOGGER.info("wait start---------------,threadName->{}", Thread.currentThread().getName());// 等待10秒钟自动唤醒 list.wait();LOGGER.info("wait end---------------,threadName->{}", Thread.currentThread().getName());}LOGGER.info("list.remove ->{}, threadName->{}", list.get(0), Thread.currentThread().getName());list.remove(0);}} catch (InterruptedException e) {e.printStackTrace();}}}, "sub1");sub1.start();// 删除元素线程2Thread sub2 = new Thread(new Runnable() {@Overridepublic void run() {try {synchronized (list) {while (list.size() == 0) {LOGGER.info("wait start---------------,threadName->{}", Thread.currentThread().getName());// 等待10秒钟自动唤醒 list.wait();LOGGER.info("wait end---------------,threadName->{}", Thread.currentThread().getName());}LOGGER.info("list.remove ->{}, threadName->{}", list.get(0), Thread.currentThread().getName());list.remove(0);}} catch (InterruptedException e) {e.printStackTrace();}}}, "sub2");sub2.start();// 增加元素线程Thread.sleep(1 * 1000);Thread addThread = new Thread(new Runnable() {@Overridepublic void run() {synchronized (list) {list.add("1");LOGGER.info("添加元素->{},threadName->{}", "1", Thread.currentThread().getName());list.notifyAll();LOGGER.info("threadName->{} 执行list.notifyAll() 发出通知唤醒所有阻塞线程", Thread.currentThread().getName());}}}, "B");addThread.start();} }
结果:
22:40:06 [cn.qlq.thread.six.Demo10]-[INFO] wait start---------------,threadName->sub1
22:40:06 [cn.qlq.thread.six.Demo10]-[INFO] wait start---------------,threadName->sub2
22:40:07 [cn.qlq.thread.six.Demo10]-[INFO] 添加元素->1,threadName->B
22:40:07 [cn.qlq.thread.six.Demo10]-[INFO] threadName->B 执行list.notifyAll() 发出通知唤醒所有阻塞线程
22:40:07 [cn.qlq.thread.six.Demo10]-[INFO] wait end---------------,threadName->sub2
22:40:07 [cn.qlq.thread.six.Demo10]-[INFO] list.remove ->1, threadName->sub2
22:40:07 [cn.qlq.thread.six.Demo10]-[INFO] wait end---------------,threadName->sub1
22:40:07 [cn.qlq.thread.six.Demo10]-[INFO] wait start---------------,threadName->sub1
9. 通知等待结合volatile实现一个多线程交替执行的例子
这个案例是为了复习wait/notify与volatile的知识点。创建20个线程,实现交替打印☆和★符号,也就是实现:下面的效果
☆★☆★☆★☆★☆★☆★☆★☆★☆★☆★
package cn.qlq.thread.six;import org.slf4j.Logger; import org.slf4j.LoggerFactory;/*** 交替打印特殊符号* 奇数线程打印☆,偶数线程打印★* * @author Administrator**/ public class Demo11 {private static final Logger LOGGER = LoggerFactory.getLogger(Demo11.class);public volatile static boolean isOddThread = true; // 标记是不是奇数线程public static void main(String[] args) throws InterruptedException {final Object lock = new Object();//10个奇数线程打印☆for (int i = 0; i < 10; i++) {new Thread(new Runnable() {@Overridepublic void run() {try {synchronized (lock) {while (!isOddThread) {lock.wait();}LOGGER.info("ThreadName->{}, print ->{}",Thread.currentThread().getName(),"☆");isOddThread = false;lock.notifyAll();}} catch (InterruptedException e) {e.printStackTrace();}}}, "odd" + i).start();}//10个偶数线程打印★for (int i = 0; i < 10; i++) {new Thread(new Runnable() {@Overridepublic void run() {try {synchronized (lock) {while (isOddThread) {lock.wait();}LOGGER.info("ThreadName->{}, print ->{}",Thread.currentThread().getName(),"★");isOddThread = true;lock.notifyAll();}} catch (InterruptedException e) {e.printStackTrace();}}}, "even" + i).start();}} }
结果:
13:54:36 [cn.qlq.thread.six.Demo11]-[INFO] ThreadName->odd0, print ->☆
13:54:36 [cn.qlq.thread.six.Demo11]-[INFO] ThreadName->even8, print ->★
13:54:36 [cn.qlq.thread.six.Demo11]-[INFO] ThreadName->odd9, print ->☆
13:54:36 [cn.qlq.thread.six.Demo11]-[INFO] ThreadName->even0, print ->★
13:54:36 [cn.qlq.thread.six.Demo11]-[INFO] ThreadName->odd1, print ->☆
13:54:36 [cn.qlq.thread.six.Demo11]-[INFO] ThreadName->even9, print ->★
13:54:36 [cn.qlq.thread.six.Demo11]-[INFO] ThreadName->odd8, print ->☆
13:54:36 [cn.qlq.thread.six.Demo11]-[INFO] ThreadName->even1, print ->★
13:54:36 [cn.qlq.thread.six.Demo11]-[INFO] ThreadName->odd2, print ->☆
13:54:36 [cn.qlq.thread.six.Demo11]-[INFO] ThreadName->even7, print ->★
13:54:36 [cn.qlq.thread.six.Demo11]-[INFO] ThreadName->odd7, print ->☆
13:54:36 [cn.qlq.thread.six.Demo11]-[INFO] ThreadName->even2, print ->★
13:54:36 [cn.qlq.thread.six.Demo11]-[INFO] ThreadName->odd3, print ->☆
13:54:36 [cn.qlq.thread.six.Demo11]-[INFO] ThreadName->even6, print ->★
13:54:36 [cn.qlq.thread.six.Demo11]-[INFO] ThreadName->odd6, print ->☆
13:54:36 [cn.qlq.thread.six.Demo11]-[INFO] ThreadName->even3, print ->★
13:54:36 [cn.qlq.thread.six.Demo11]-[INFO] ThreadName->odd4, print ->☆
13:54:36 [cn.qlq.thread.six.Demo11]-[INFO] ThreadName->even5, print ->★
13:54:36 [cn.qlq.thread.six.Demo11]-[INFO] ThreadName->odd5, print ->☆
13:54:36 [cn.qlq.thread.six.Demo11]-[INFO] ThreadName->even4, print ->★
总结:
wait()和notify()、wait(long)、notifyAll()都必须在获得资源锁的情况下使用,而且获得哪个资源锁才能调用锁的wait()和notify()方法,否则会抛出IllegalMonitorStateException异常。
每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列。就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程。一个线程被唤醒后,才会进入就绪队列,等待CPU的调度;反之,一个线程被wait之后就进入阻塞队列,等待下一次被唤醒。
wait()使线程进入waiting阻塞状态,而且会立即释放锁并且暂停后面的代码。
wait(long)是超过long时间之后会自动解除阻塞(从阻塞队列转为就绪队列)。该方法会使线程进行超时等待状态,与sleep(long)一样。
notify()每次唤醒一个线程,是随机唤醒,锁阻塞对象中的一个线程变为可运行状态,重新强占CPU调度强占资源锁,而且notify()不会释放锁,只有执行完之后才能释放锁
notifyAll()唤醒所有的阻塞线程,使阻塞队列转为就绪队列,然后从就绪队列中选取一个重新强占CPU调度强占资源锁,而且notify()不会释放锁,只有执行完之后才能释放锁