JAVA并发编程(三)-wait-notify


一、 🧡简介

  在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();

执行结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值