wait/notify实现线程间的通信

  使线程之间进行通信之后,系统间的交互性更加强大,在大大提高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()不会释放锁,只有执行完之后才能释放锁

  

 

转载于:https://www.cnblogs.com/qlqwjy/p/10099482.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/364623.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

洛谷 P3367 【模板】并查集

嗯... 题目链接&#xff1a;https://www.luogu.org/problemnew/show/P3367 并查集可以支持的操作&#xff1a;“并”和“查”。然后这道题主要就是考察这两种操作。将每一个点的“父亲”初始化为自己&#xff0c;然后分别进行“并”和“查”。 “并”&#xff1a;用递归函数fin…

jquery3和layui冲突导,致使用layui.layer.full弹出全屏iframe窗口时高度152px问题

项目中使用的jquery版本是jquery-3.2.1&#xff0c;在使用layui弹出全屏iframe窗口时&#xff0c;iframe窗口顶部总是出现一个152px高的滚动窗口无法实现真正全屏&#xff0c;代码如下&#xff1a; <!DOCTYPE html> <html> <head><meta charset"utf-…

ADF Faces。 立即的自定义客户端事件

在本文中&#xff0c;我将重点介绍ADF Faces Javascript API方法以从客户端触发自定义事件。 例如&#xff1a; function cliListener(actionEvent) {AdfCustomEvent.queue(actionEvent.getSource(), "servListener",null, true);}我们可以使用af&#xff1a;client…

Genymotion模拟器安装ARM架构编译应用失败解决方案

我们在安装一些应用到Genymotion模拟器会提示&#xff1a;adb: failed to install xx.apk: Failure [INSTALL_FAILED_NO_MATCHING_ABIS: Failed to extract native libraries, res-113] 原因是Genymotion采用的编译方式是x86&#xff0c;默认不支持ARM架构编译的应用&#xff0…

CentOS7.5 yum 安装与配置MySQL5.7.24

安装环境&#xff1a;CentOS7 64位 MINI版&#xff0c;安装MySQL5.7 1、配置YUM源 在MySQL官网中下载YUM源rpm安装包&#xff1a;https://dev.mysql.com/downloads/repo/yum/ 下面已经提供一个YUM源安装包,如果不需要特定版本可直接使用我提供的5.7.24版本 # 下载mysql源安装包…

5种改善服务器日志记录的技术

在最近的时间里&#xff0c;我们已经看到了许多工具可以帮助您理解日志。 诸如Scribe和LogStash之类的开源项目&#xff0c;诸如Splunk之类的本地工具以及诸如SumoLogic和PaperTrail之类的托管服务。 这些都可以帮助您将大量日志数据减少为更有意义的内容。 它们共有一个共同点…

java excel处理框架,Java三方—-excel框架之POI的使用一

Apache POI是Apache软件基金会的开放源码函式库&#xff0c;POI提供API给Java程序对Microsoft Office格式档案读和写的功能。pdf框架之IText的使用&#xff0c;参见我的博客&#xff1a;Java三方—->pdf框架之IText的使用。今天我们开始POI中Excel部分的学习。POI框架的简单…

关于background-*的一些属性

1、盒模型 盒模型从外到内一次为&#xff1a;margin-box、border-box、padding-box、content-box。 2、一些属性设置的相对位置 ⑴background-position的属性值&#xff08;top/right/bottom/left/center&#xff09;起始位置是相对于padding-box外边沿开始的&#xff0c;…

Outlook邮件的右键菜单中添加自定义按钮

customUI代码如下&#xff1a; <customUI xmlns"http://schemas.microsoft.com/office/2009/07/customui"><contextMenus><contextMenu idMso"ContextMenuMailItem"> <button id"button1" label"修改件名"…

vue 项目的I18n国际化之路

I18n (internationalization ) ---未完善 产品国际化是产品后期维护及推广中重要的一环&#xff0c;通过国际化操作使得产品能更好适应不同语言和地区的需求 国际化重点&#xff1a;1、 语言语言本地化2、 文化颜色、习俗等3、 书写习惯日期格式、时区、数字格式、书写方向备…

使用IAM保护您的AWS基础架构

在开发新产品并发现合适的产品市场时&#xff0c;每个团队都需要快速行动。 尤其是初创公司&#xff0c;因为公司的整个未来都取决于快速找到为您的产品付款的人。 对于初创企业和其他团队来说&#xff0c; Amazon Web Services是令人难以置信的工具&#xff0c;可以快速构建其…

mysql文件软连接失败,解决打包软链接打包失败问题

一般情况下打包文件时&#xff0c;如果直接打包软连接会导致打包失败&#xff0c;即没有将要打包的内容打包进去&#xff0c;这里提供tar打包参数-h[rootlocalhost ~]# ll /etc/rc.locallrwxrwxrwx. 1 root root 13 Nov 24 00:45 /etc/rc.local -> rc.d/rc.local[rootlocalh…

快速掌握前端 专为Java程序员定制

Javascript 例子 修改页面内容 js 代码位置 <script>// js 代码 </script>引入 js 脚本 <script src"js脚本路径"></script>注意&#xff0c;到了框架之后&#xff0c;引入方式会有不同 1. 变量与数据类型 声明变量 1) let ⭐️ l…

Solidity中如何判断mapping中某个键是否为空呢?

Solidity中如何判断mapping中某个键是否为空呢&#xff1f; 一.比较标准的做法是建立一个专门和value相关的结构体&#xff0c;用一个布尔型变量来看是否这个key所对应的value被赋过值 代码如下&#xff1a; pragma solidity ^0.4.19;contract UserTest {struct User{string na…

mac安装了多版本php 卸载,mac 安装多版本PHP

前言相信大家在mac 安装PHP多版本的时候也遇到了很多坑# brew install php56# brew install php70这样安装的话肯定会报错的&#xff0c;因为brew存在软连接这个时候我们第一步&#xff1a;brew unlink php56 或者 brew unlink php70这个步骤是关闭掉PHP的软连接第二步&#x…

jQuery的on绑定click和直接绑定click区别

状况之外 在之前的公司并没有遇到这个问题&#xff0c;也就没有深究。直到自己换了现在的公司&#xff0c;刚来第二天就开始写别人写到一半的项目&#xff0c;很无奈&#xff0c;不是原生就是jquery&#xff0c;由于项目急&#xff0c;已经来不及切换框架重新布局&#xff0c;只…

Thymeleaf与Spring集成(第1部分)

1.引言 本文重点介绍如何将Thymeleaf与Spring框架集成。 这将使我们的MVC Web应用程序能够利用Thymeleaf HTML5模板引擎&#xff0c;而不会丢失任何Spring功能。 数据层使用Spring Data与mongoDB数据库进行交互。 该示例包含在酒店的单页Web应用程序中&#xff0c;从中我们可以…

oracle 老白,老白学编程 - Netdata学习 - numa

Numa 介绍NUMA,即Non-Uniform Memory Access Architecture&#xff0c;非统一内存访问架构。背景传统的SMP中&#xff0c; 所有处理器共享系统总线&#xff0c;当cpu数目增大时&#xff0c; 系统总现竞争就相应增加&#xff0c;会成为系统的瓶颈&#xff0c;所以SMP系统的CPU数…

如何安装 Angular CLI 并且检查 CLI 的版本

想在系统中安装 Angular CLI &#xff0c;如何进行安装并且如何检查 CLI 的版本&#xff1f; 可以使用命令&#xff1a; npm install -g angular/cli 进行安装。 使用命令 ng version 来查看 Angular 的 CLI 的版本 转载于:https://www.cnblogs.com/huyuchengus/p/10879166.htm…

2018-2019-2 网络对抗技术 20165329 Exp 8 Web基础

2018-2019-2 网络对抗技术 20165329 Exp 8 Web基础 原理与实践说明 实践内容概述基础问题回答实践过程记录 1.Web前端&#xff1a;HTML2.Web前端&#xff1a;javascipt3.Web后端&#xff1a;MySQL基础4.Web后端&#xff1a;编写PHP网页5.最简单的SQL注入&#xff0c;XSS攻击测试…