AI智能
改变未来

线程间的通信及等待之wait()与notify() | notifyAll() 与 join()方法


总结

1:wait()方法

1:在调用此方法之前,线程必须获得该对象的对象级别的锁,及只能在同步方法或同步代码块中使用。
2: 调用此方法后线程将从运行状态进入阻塞队列,同时释放对象锁(与sleep()不同)。直到该对象调用notify() | notifyAll() 后该线程才能够拥有重新竞争锁的机会。
若线程处于wait状态时执行了interrupt()方法,线程会抛出 InterruptedExcuption 异常

2:wait(long)方法

等待时间内是否有其他线程对锁进行唤醒,若没有,则到时间后自动唤醒。

3: notify()

  • 在调用此方法之前,线程必须获得该对象的对象级别的锁,及只能在同步方法或同步代码块中使用。
  • 该方法用来通知那些可能等待该对象的对象锁的线程,如果有多个等待线程,则由线程规划器随机挑选出其中一个程wait状态的线程,使之等待获得该对象的锁
  • 调用该方法后,当前线程不会立即释放锁,需要当当前notify() 方法所处的同步代码块中的方法全部执行完后,当前线程才会释放锁
  • 使用此方法要注意 唤醒同类 例如生产者消费者模式中,消费者消费完成后,正常情况下执行此方法是要唤醒生产者,但是此方法唤醒阻塞线程是随机的,所有有可能唤醒的是同样处于阻塞状态的消费者线程。

4:notifyAll()

1 :其作用与notify()相同,不同的是notify()一次只能唤醒一个线程,而notifyAll()则可以唤醒当前对象所对应的全部阻塞线程。
2:join()方法

5:join()方法

1:在很多情况下主线程结束的时间都早于子线程,若想要等到子线程结束在结束主线程,则可以用jion()方法。 方法join()的作用便是等待

调用 join

的线程对象销毁。

2:在join的过程中,若当前线程对象被中断,则当前线程抛出异常。

3: 执行join方法后,线程会进入等待(WAITING)状态

4: join(long)与sleep(long)的区别:

  • 方法join的功能在内部是使用wait(long)的方法来实现的,所有此方法具有释放锁的功能。
  • sleep(long)不具备释放锁的功能。

6:wait()与notify()方法实践

示例一

public class WaitNotifyThread {private static volatile Object resA = new Object();private static volatile Object resB = new Object();public static void main(String[] args) {Thread thread1 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (resA){System.out.println(\"ThreadA 获取到锁  resA\");synchronized (resB){System.out.println(\"ThreadA 获取到锁  resB\");try {System.out.println(\"ThreadA 释放锁  resA\");/*** 执行wait() | notify()方法前,该线程必须获得该对象的对象级别的锁* 否则抛异常*/resA.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}});Thread thread2 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (resA){System.out.println(\"ThreadB 获取到锁  resA\");System.out.println(\"线程ThreadB 中释放resA锁\");//调用此方法后,此线程依旧不能获得resB对象。//因为调用此方法后,当前线程不会立马释放该对象的锁,呈现wite状态的线程也并不能//立马获取该对象的锁,要等到执行notify()方法的线程将程序执行完。//因此接下来的synchronized (resB)因无法获得对象而无法执行。整个系统处于阻塞状态。resA.notify();synchronized (resB){System.out.println(\"ThreadB 获取到锁  resB\");try {System.out.println(\"ThreadB 释放锁  resA\");resA.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}});thread1.start();thread2.start();}}

示例二
利用wait和notify实现两线程循环打印奇偶数

/*** 两个线程循环打印0到100的奇偶数*/public class WaitTest {private static final Object lock = new Object();private static int count =0;public static void main(String[] args) throws InterruptedException {new Thread(new HasAnEvenNumber(lock,count),\"偶数\").start();//Thread.sleep(100);new Thread(new HasAnEvenNumber(lock,count),\"奇数\").start();}}class HasAnEvenNumber implements Runnable{private static int count;private Object lock;public HasAnEvenNumber(Object lock,int count) {this.lock = lock;this.count = count;}@Overridepublic void run() {synchronized (lock){while (count <= 100){System.out.println(Thread.currentThread().getName() +\"  \" + count++);lock.notify();if (count<=100){try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}}}}}

生产者与消费者

一个生产者与一个消费者

public class Producers_And_Consumers {public static void main(String[] args) {MyStack myStack = new MyStack();P_Thread p = new P_Thread(myStack);C_Thread c = new C_Thread(myStack);Thread threadP = new Thread(p);threadP.start();Thread threadC = new Thread(c);threadC.start();}}/*** 生产者*/class MyStack{ArrayList<String> arrayList = new ArrayList<>();synchronized public void set(){if (arrayList.size() == 1){try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}arrayList.add(\"张三\");/** 生产后通知消费者消费*/this.notify();System.out.println(\"set : \"+arrayList.size());}synchronized public void get(){if (arrayList.size() == 0){try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(\"get : \"+arrayList.get(0));arrayList.remove(0);/** 消费后通知生产者生产*/this.notify();}}/*** 多线程之生产者调用*/class P_Thread implements Runnable{private MyStack myStack;public P_Thread(MyStack myStack){this.myStack = myStack;}@Overridepublic void run() {while (true){myStack.set();}}}class C_Thread implements Runnable{private MyStack myStack;public C_Thread(MyStack myStack){this.myStack = myStack;}@Overridepublic void run() {while (true){myStack.get();}}}

出现假死的情况

一个生产者多个消费者的情况

改变上述代码public static void main(String[] args) {MyStack myStack = new MyStack();P_Thread p = new P_Thread(myStack);C_Thread c = new C_Thread(myStack);Thread threadP = new Thread(p);threadP.start();Thread threadC1 = new Thread(c);Thread threadC2 = new Thread(c);Thread threadC3 = new Thread(c);Thread threadC4 = new Thread(c);Thread threadC5 = new Thread(c);threadC1.start();threadC2.start();threadC3.start();threadC4.start();threadC5.start();}

此时看控制台

控制台并未在打印,及生产者与消费者不再执行,同时线程并未停止,此时线程便处于阻塞状态。
出现这种情况的原因便是

唤醒了同类线程

解释: 当线程处于等待状态时,生产者被某个线程唤醒,进行生产。生产完成后随机唤醒处于阻塞状态的消费者。消费者执行

arrayList.remove(0);

进行消费。消费完成后执行notify()方法,但是此时唤醒的并不是生产者,而是同类的消费者,接下来唤醒的消费者继续执行

arrayList.remove(0);

方法,但此时集合为空,所以便出现数组下标越界的情况,抛出异常。消费者线程暂停,同时生产者线程因为未被唤醒处于阻塞状态。

解决方式: 将生产者与消费者中的

if (arrayList.size() == 1)

换成while循环

还有一种假死的情况是 消费者同类唤醒,造成生产者始终处于未被消费的状态,从而造成假死
解决方式:将唤醒的

notify()

换成

notifyAll()

join()方法代码展示

public class JoinThread {public static void main(String[] args) throws InterruptedException {Join join = new Join();Thread thread = new Thread(join);thread.start();thread.join();System.out.println(\"主线程 ···\");}}class Join implements Runnable{@Overridepublic void run() {System.out.println(\"子线程 begin~~~\");try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}}}
赞(0) 打赏
未经允许不得转载:爱站程序员基地 » 线程间的通信及等待之wait()与notify() | notifyAll() 与 join()方法