多线程中的notify和wait方法的使用

本文通过实例详细解析了Java线程同步中wait()与notify()的使用方法,演示了不同线程间的交互过程,帮助读者理解如何正确实现线程间的等待与唤醒。

Java线程同步中wait()和notify()简洁例子

搞懂这两个的用法之前,请你务必搞懂线程同步的道理,否则,下面这一大篇你应该是看不懂的。

wait()和notify()一系列的方法,是属于对象的,不是属于线程的。它们用在线程同步时,synchronized语句块中。

我们都知道,在synchronized语句块中,同一个对象,一个线程在执行完这一块代码之前,另一个线程,如果传进来的是同一个object,是不能进入这个语句块的。也就是说,同一个对象

是不能同时被两个线程用来进入synchronized中的。这就是线程同步。

废话不多说,先用通俗一点的语言来解释一下wait()和notify():

wait()意思是说,我等会儿再用这把锁,CPU也让给你们,我先休息一会儿!

notify()意思是说,我用完了,你们谁用?

也就是说,wait()会让出对象锁,同时,当前线程休眠,等待被唤醒,如果不被唤醒,就一直等在那儿。

notify()并不会让当前线程休眠,但会唤醒休眠的线程。

先看第一个例子!

public class ThreadF {

public static void main(String[] args) {

final Object object = new Object();

Thread t1 = new Thread() {

public void run()

{

synchronized (object) {

System.out.println("T1 start!");

try {

object.wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("T1 end!");

}

}

};

Thread t2 = new Thread() {

public void run()

{

synchronized (object) {

System.out.println("T2 start!");

object.notify();

System.out.println("T2 end!");

}

}

};

t1.start();

t2.start();

}

}

这第一个例子很简单,写了两个线程(分别是两个类,两个run方法)。

两个run方法之间没有关系,但是,他们都用了同一个object!

仔细看,T1里面主要写了个wait(),而T2里面主要写了个notify()。

我们看到执行结果是:

T1 start!

T2 start!

T2 end!

T1 end!

流程可以这样解释:

T1启动,让出锁,让出CPU,T2获得CPU,启动,唤醒使用了object的休眠的线程,T1被唤醒后等待启动,T2继续执行,T2执行完,T1获得CPU后继续执行。

值得一提的是,再强调一遍:

wait会让出CPU而notify不会,notify重在于通知使用object的对象“我用完了!”,wait重在通知其它同用一个object的线程“我暂时不用了”并且让出CPUT。

所以说,看上面的顺序,

T2 start!

T2 end!

是连续的,说明它并没有因调用了notify而暂停!

那么,如

果两个线程都写wait没有线程写notify会有什么现象呢?试一下就知道了。

结果是,

T1 start!

T2 start!

然后就是一直等待!

道理很显然,T1先启动,然后wait了,T2获得了锁和CPU,在没有其它线程与它竞争的情况下,T2执行了,然后也wait了。

在这里,两个线程都在等待,没有其它线程将它们notify,所以结果就是无休止地等下去!

至少说明了一点,wait后如果没有其它线程将它notify,是绝不可能重新启动的。不可能因为目前没有线程占用CPU,某一个正在等待的线程就自动重启。

下面,我再把它改一下,写四个线程,分别是

T1 wait()

T2 notify()

T3 notify()

T4 wait()

public class ThreadF {

public static void main(String[] args) {

final Object object = new Object();

Thread t1 = new Thread() {

public void run()

{

synchronized (object) {

System.out.println("T1 start!");

try {

object.wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("T1 end!");

}

}

};

Thread t2 = new Thread() {

public void run()

{

synchronized (object) {

System.out.println("T2 start!");

object.notify();

System.out.println("T2 end!");

}

}

};

Thread t3 = new Thread() {

public void run()

{

synchronized (object) {

System.out.println("T3 start!");

object.notify();

System.out.println("T3 end!");

}

}

};

Thread t4 = new Thread() {

public void run()

{

synchronized (object) {

System.out.println("T4 start!");

try {

object.wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("T4 end!");

}

}

};

t1.start();

t2.start();

t3.start();

t4.start();

}

}

首先,大家知道,线程启动的顺序,和代码的先后顺序,理论上是没有关系的!

比如我这儿写的是按T1-T2-T3-T4的先后顺序先后start(),但实际上谁先启动,是有一定几率的。

执行上面代码,有两种结果:

一种是刚好wait两次,notify两次,notify在wait之后执行,刚好执行完。

另一种是,也是刚好wait两次,notify两次,但是,notify在wait之前执行,于是,至少会有一个线程由于后面没有线程将它notify而无休止

地等待下去!

我摘选了两种情况的输出结果,仅供参考:

1、可以执行结束的情况:

T1 start!

T2 start!

T2 end!

T1 end!

T4 start!

T3 start!

T3 end!

T4 end!

执行流程是:

T1启动,wait,T2获得锁和CPU,T2宣布锁用完了其它线程可以用了,然后继续执行,T2执行完,T1被刚才T2唤醒后,等待T2执行完后,抢到了CPU,T1执行,

T1执行完,T4获得CPU,启动,wait,T3获得了锁和CPU,执行,宣布锁用完了,其它线程可以用了,然后继续执行,T3执行完,已经被唤醒并且等待已久的T4

得到CPU,执行。

2、不能执行结束,有线程由于没有其它线程唤醒,一直在等待中:

T1 start!

T2 start!

T2 end!

T1 end!

T3 start!

T3 end!

T4 start!

执行流程:

T1启动,wait,让出CPU和锁,T2得以启动。T2启动,并唤醒一个线程,自己继续执行。被唤醒的线程,也就是T1等待启动机会。

T2执行完,T1抢到了CPU,执行,并结束。

这时,只剩下T3和T4,在此时,两个线程的机会均等。

但是,T3抢到了CPU,于是它执行了,而且唤醒了线程,虽然此时并没有线程休眠。说白了,它浪费了一次notify。T3顺利执行完。

这时,终于轮到了T4,它启动了,wait了,但是,后面已经没有线程了,它的wait永远不会有线程帮它notify了!

于是,它就这么等着!

请仔细看执行流程,看懂,再自己做一下试验。

仔看看,你会看到,凡是当前线程的run方法里面写了notify,有了start马上就会end,而如果是写的wait,有了start,下一个绝对不是输出这个线程的end。

所以说,T2和T3由于是写的notify,它们的start和end总是成对出现。而T1和T4由于是写的wait,它们start后,下一个绝不可能是它的end。

最后再提醒一下,我们的wait和notify是针对同一个object的,而非线程。我们这一篇都在讲对象锁,而不是线程。

顺便说一下,如果没有线程在wait,调用notify是不会有什么问题的,就像这样:

public class ThreadG {

public static void main(String[] args) {

final Object object = new Object();

Thread t1 = new Thread() {

public void run()

{

synchronized (object) {

System.out.println("T1 start!");

object.notify();

System.out.println("T1 end!");

}

}

};

t1.start();

}

}

T1 start!

T1 end!


声明:二次转载,未发现原文链接地址!原著见谅。


使用 `Thread.sleep()`、`wait()` `notify()` 函数构建生产者-消费者模型可能会遇到一些挑战,因为它们没有提供像 `BlockingQueue` 这样的内置阻塞机制,而且它们需要手动管理资源锁条件。下面是一个简单的示例,展示了如何使用这两个方法,但这种方式并不推荐用于实际生产环境,因为它缺乏现代并发库提供的安全性效率。 ```java public class SharedResource { private boolean available; // 表示资源是否可供消费 private Object lock = new Object(); // 模拟生产者添加数据到资源 public synchronized void producer() throws InterruptedException { while (!available) { // 当资源不可用时进入等待 wait(); // 生产者等待 } available = false; // 标记资源已使用 System.out.println("Producer added data to resource"); notify(); // 唤醒一个等待的消费者 } // 模拟消费者消耗数据 public synchronized void consumer() throws InterruptedException { while (available) { // 当资源可用时进入等待 wait(); // 消费者等待 } available = true; // 标记资源释放 System.out.println("Consumer consumed data from resource"); notify(); // 唤醒一个等待的生产者 } } public class Main { public static void main(String[] args) { SharedResource resource = new SharedResource(); Thread producerThread = new Thread(() -> { try { resource.producer(); } catch (InterruptedException e) { e.printStackTrace(); } }); Thread consumerThread = new Thread(() -> { try { resource.consumer(); } catch (InterruptedException e) { e.printStackTrace(); } }); producerThread.start(); consumerThread.start(); } } ``` 在这个示例中,生产者消费者都在循环中检查资源的状态,并通过 `wait()` `notify()` 来协调。但是这种方式存在潜在的问题,比如死锁、资源竞争等问题,因为没有明确的资源分配释放策略。在实践中,建议使用专门设计并发的工具类,如 `ReentrantLock` `Condition`,或者 `java.util.concurrent` 的其他高级并发组件,以保证代码的安全性可靠性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值