文章目录
一、 🧡简介
在Java中,wait() 和 notify() 方法是Object类的一部分,用于线程间的通信和同步。这些方法通常用于一种情况,即一个线程需要等待某个条件满足才能继续执行,而另一个线程需要在条件满足时发出通知。
API接口:
wait() 方法:用于使一个线程等待,直到另一个线程为相同的对象调用 notify() 或 notifyAll() 方法。
notify() 方法“:唤醒当前正在等待该对象的某个线程。唤醒哪个线程不是确定的,取决于JVM的实现。
notifyAll() 方法:唤醒所有当前正在等待该对象的线程。
它们都属于Object对象的方法,必须获得到此对象的锁,才能够调用者几个方法。
二、💛api演示
(1)wait()和notify()
public class TestWaitNotify {
final static Object object = new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
synchronized (object) {
log.debug("执行代码");
try {
object.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.debug("其他代码");
}
},"t1").start();
new Thread(() -> {
synchronized (object) {
log.debug("执行代码");
try {
object.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.debug("其他代码");
}
},"t2").start();
//睡眠两秒
TimeUnit.SECONDS.sleep(2);
synchronized (object){
log.debug("唤醒其中一个线程");
object.notify();
}
}
}
执行结果:
我们可以看到这里notify()这个方法,只唤醒了t1的线程。
(2)wait()和notifyAll()
@Slf4j(topic = "TestWaitNotify")
public class TestWaitNotify {
final static Object object = new Object();
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
synchronized (object) {
log.debug("执行代码");
try {
object.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.debug("其他代码");
}
},"t1").start();
new Thread(() -> {
synchronized (object) {
log.debug("执行代码");
try {
object.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.debug("其他代码");
}
},"t2").start();
//睡眠两秒
TimeUnit.SECONDS.sleep(2);
synchronized (object){
log.debug("唤醒其中一个线程");
object.notifyAll();
}
}
}
我们可以看到这里notifyAll把t1和t2线程都唤醒了。
(3)wait(long timeout)
new Thread(() -> {
synchronized (object) {
log.debug("执行代码");
try {
object.wait(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.debug("其他代码");
}
},"t1").start();
执行结果:
我们可以看到这里我们设置了wait的入参是1000ms,当到了1000ms之后,这里就不会再等待下去了,而是直接唤醒当前线程,继续执行后面的代码。
注意:无参的wait()会一直等待直到有notify或notifyAll()进行唤醒
💚三、sleep和wait的区别
(1)sleep是Thread方法,而wait是Object方法。
(2)sleep不需要强制和synchronized配合使用,但wait需要和synchronized一起用。
(3)sleep在睡眠的同时,不会释放对象锁,但wait在等待的时候会释放对象锁。
接下来我们来分析一下以下代码
@Slf4j(topic = "TestCorrectPostureStep1")
public class TestCorrectPostureStep1 {
static final Object room = new Object();
static boolean hasCigaratte = false;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
synchronized (room) {
log.debug("有烟没?[{}]", hasCigaratte);
while (!hasCigaratte) {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
log.debug("有烟没?[{}]", hasCigaratte);
if (hasCigaratte) {
log.debug("开始干活");
}
}
}, "小南").start();
for (int i = 0; i < 5; i++) {
new Thread(()->{
synchronized (room){
log.debug("开始干活");
}
},"其他人"+i).start();
}
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
hasCigaratte=true;
log.debug("来送烟喽");
},"送烟的").start();
}
}
执行结果:
我们可以看到这里当小南没有烟的时候,依然占用着房间的使用权,只有等到送烟的人到了,小南才去干活,而小南干完活以后,让出了房间的使用权,才能够让其他人去干活。
那这里就存在一个问题,当小南占用着房间的时候,其他人没法干活
我们可以改成使用wait()notify()来改良这段代码。
@Slf4j(topic = "TestCorrectPostureStep1")
public class TestCorrectPostureStep1 {
static final Object room = new Object();
static boolean hasCigaratte = false;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
synchronized (room) {
log.debug("有烟没?[{}]", hasCigaratte);
while (!hasCigaratte) {
try {
room.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
log.debug("有烟没?[{}]", hasCigaratte);
if (hasCigaratte) {
log.debug("开始干活");
}
}
}, "小南").start();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
synchronized (room) {
log.debug("开始干活");
}
}, "其他人" + i).start();
}
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
synchronized (room) {
hasCigaratte = true;
room.notify();
log.debug("来送烟喽");
}
}, "送烟的").start();
}
}
执行结果:
我们可以看到这里当小南没有烟的时候,小南线程进入了等待的队列,同时释放了room的控制权,让其他线程可以进房间内干活,直到送烟的线程唤醒小南线程才拿回room控制权开始干活。
💙四、notify()和notifyAll()虚假唤醒
观察一下以下代码
@Slf4j(topic = "TestCorrectPostureStep1")
public class TestCorrectPostureStep1 {
static final Object room = new Object();
static boolean hasCigaratte = false;
static boolean hasTakeOut = false;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
synchronized (room) {
log.debug("有烟没?[{}]", hasCigaratte);
try {
room.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.debug("有烟没?[{}]", hasCigaratte);
if (hasCigaratte) {
log.debug("开始干活");
}else{
log.debug("没干成活");
}
}
}, "小南").start();
new Thread(() -> {
synchronized (room) {
log.debug("有外卖没?[{}]", hasTakeOut);
try {
room.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.debug("有外卖没?[{}]", hasTakeOut);
if (hasTakeOut) {
log.debug("开始吃饭");
}else{
log.debug("没吃上饭 ");
}
}
}, "小女").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
synchronized (room) {
hasTakeOut = true;
room.notify();
log.debug("来送外卖喽");
}
}, "送外卖喽").start();
}
}
执行结果:
我们可以看到当送外卖来了的时候,进行了notify,但是并没有唤醒小女,而是唤醒了小南,小南醒来后发现并没有烟,所以干不成活,那么遇到这种情况该怎么办呢?细心的同学可以发现这里我只要改动成notifyAll()把小南小女都唤醒了,这样就可以了。
room.notifyAll();
执行结果:
如果我们想小南没烟来,也继续歇着,我们可以这样写
while(!hasCigaratte){
log.debug("没有烟,先歇着");
try {
room.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
💜五、设计模式-保护性暂停
(1)定义
即Guarded Suspension,用在一个线程等待另一个线程的执行结果。
要点
1、有一个结果需要从一个线程传递到另一个线程,让他们关联同一个GuardedObject。
2、如果 有结果不断从一个线程到另一个线程那么就可以使用消息队列(生产者/消费者)
3、JDK中,join的实现,Future的实现,就是采用这种模式。
4、因为要等待另一方的记过,因此归类到同步模式。
(2)编写代码
那我们就来编写一下代码吧。
@Slf4j(topic = "TestProtectivePause")
public class TestProtectivePause {
public static void main(String[] args) {
Guared guared = new Guared();
new Thread(() -> {
//等待结果
log.debug("等待结果");
Object o = guared.get();
log.debug(o.toString());
}, "t1").start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
guared.complete("结果数据");
}, "t2").start();
}
}
class Guared {
private Object response;
//获取结果
public Object get() {
synchronized (this) {
//没有结果
while (response == null) {
try {
this.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
return response;
}
//产生结果
public void complete(Object response) {
synchronized (this) {
this.response = response;
this.notifyAll();
}
}
}
执行结果:
(3)增加超时效果
@Slf4j(topic = "TestProtectivePause")
public class TestProtectivePause {
public static void main(String[] args) {
Guared guared = new Guared();
new Thread(() -> {
//等待结果
log.debug("等待结果");
Object o = guared.get(1000);
log.debug("结果是{}",o);
}, "t1").start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
guared.complete("结果数据");
}, "t2").start();
}
}
class Guared {
private Object response;
//获取结果
//timeout表示要等待多久
public Object get(long timeout) {
synchronized (this) {
//开始时间
long begin = System.currentTimeMillis();
//经历的时间
long passed = 0;
//没有结果
while (response == null) {
//如果经历的时间大于超时时间,则直接退出
if (passed >= timeout) {
break;
}
try {
//这里等待的时间应为超时时间减去已经经历的时间,因为会存在虚假唤醒的情况
//当线程在超时前唤醒,但response依然为null,会进入下次循环,下次循环的时候等待时间不应该为超时等待的时间,应为超时时间减去已经历的时间
this.wait(timeout - passed);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//经历时间应为当前时间减去开始的时间
passed = System.currentTimeMillis() - begin;
}
}
return response;
}
//产生结果
public void complete(Object response) {
synchronized (this) {
this.response = response;
this.notifyAll();
}
}
}
执行结果:
🤎六、生产者/消费者模式
(1)定义
1、与前面的保护性暂停中的GuardObject不同,不需要产生结果和消费结果的线程一一对应。
2、消费队列可以用来平衡生产和消费的线程资源。
3、生产者仅负责产生结果数据,不关心数据如何处理,而消费者专心处理结果数据。
4、消息队列是有容量限制的,满时不会再加入数据,空时不会消耗数据。
5、JDK中各种阻塞队列,采用的就是这种模式。
(2)编写代码
@Slf4j(topic = "TestMessageQueue")
public class TestMessageQueue {
public static void main(String[] args) {
MessageQueue queue = new MessageQueue(2);
for (int i = 0; i < 3; i++) {
int id=i;
new Thread(() -> {
queue.put(new Message(id, "值" + id));
}, "生产者" + i).start();
}
new Thread(() -> {
}, "消费者").start();
}
}
@Slf4j(topic = "MessageQueue")
class MessageQueue {
//消息的队列集合
private LinkedList<Message> list = new LinkedList<>();
//队列容量
private int capcity;
public MessageQueue(int capcity) {
this.capcity = capcity;
}
//获取消息
public Message take() {
synchronized (list) {
while (list.isEmpty()) {
log.debug("队列为空,消费者线程等待");
try {
list.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
//从队列头部获取消息并返回
Message message = list.removeFirst();
list.notifyAll();
return message;
}
}
//存入消息
public void put(Message message) {
synchronized (list) {
while (list.size() == capcity) {
log.debug("队列已满,生产者线程等待");
try {
list.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
list.addLast(message);
log.debug("已生产消息");
list.notifyAll();
}
}
}
@Getter
@AllArgsConstructor
@ToString
final class Message {
private int id;
private Object value;
}
执行结果
这里当生产者生产出三个,而容量为2的时候,消费者并没有消费行为,会导致第三个生产者无法进行生产。
接下来我们来修改一下消费者的代码。
new Thread(() -> {
while(true){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
queue.take();
}
}, "消费者").start();
执行结果: