三个方法
调用需注意细节
实例
等待唤醒机制就是⽤于解决线程间通信的问题的,使⽤到的3个⽅法的含义如下:
- wait:线程不再活动,不再参与调度,进⼊ wait set 中,因此不会浪费 CPU 资源,也不会去竞争锁 了,这时的线程状态即是 WAITING。它还要等着别的线程执⾏⼀个特别的动作,也即是“通知( notify )”在这个对象上等待的线程从 wait set 中释放出来,重新进⼊到调度队列( ready queue )中。
- notify:则选取所通知对象的 wait set 中的⼀个线程释放;例如,餐馆有空位置后,等候就餐最久的 顾客最先⼊座。
- notifyAll:则释放所通知对象的 wait set 上的全部线程。
调⽤wait和notify⽅法需要注意的细节
- wait⽅法与notify⽅法必须要由同⼀个锁对象调⽤。因为:对应的锁对象可以通过notify唤醒使⽤同⼀ 个锁对象调⽤的wait⽅法后的线程。
- wait⽅法与notify⽅法是属于Object类的⽅法的。因为:锁对象可以是任意对象,⽽任意对象的所属 类都是继承了Object类的。
- wait⽅法与notify⽅法必须要在同步代码块或者是同步函数中使⽤。因为:必须要通过锁对象调⽤这2 个⽅法。
实例一:
包⼦铺线程⽣产包⼦,吃货线程消费包⼦。当包⼦没有时( 包⼦状态为false ),吃货线程等待,包⼦铺线程⽣产 包⼦( 即包⼦状态为true ),并通知吃货线程( 解除吃货的等待状态 ),因为已经有包⼦了,那么包⼦铺线程进 ⼊等待状态。接下来,吃货线程能否进⼀步执⾏则取决于锁的获取情况。如果吃货获取到锁,那么就执⾏吃包⼦动 作,包⼦吃完( 包⼦状态为false ),并通知包⼦铺线程( 解除包⼦铺的等待状态 ),吃货线程进⼊等待。包⼦ 铺线程能否进⼀步执⾏则取决于锁的获取情况。
包子资源类:
package Thread_a;
public class BaoZi {
private int count = 1;
private boolean flag = false;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
package Thread_a;
//测试类
public class BoziText {
public static void main(String[] args) {
BaoZi bz = new BaoZi();
ChiHuo chiHuo = new ChiHuo(bz);
BoZiPu boZiPu = new BoZiPu(bz);
chiHuo.start();
boZiPu.start();
}
}
//吃货线程类
class ChiHuo extends Thread{
private BaoZi bz;
public ChiHuo(BaoZi bz) {
this.bz = bz;
}
public void run(){
while (bz.getCount() < 10) {
synchronized (bz) {
if (bz.isFlag() == false) {
System.out.println("包子吃完了>>没有包子了");
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("吃货吃包子>>包子吃完了");
bz.setFlag(false);
bz.setCount(bz.getCount()+1);
System.out.println("包子吃完了>>");
//唤醒等待线程(包子铺)
bz.notify();
}
}
System.out.println("吃货吃饱了");
}
}
//包子铺线程类
class BoZiPu extends Thread{
private BaoZi bz;
public BoZiPu(BaoZi bz) {
this.bz = bz;
}
public void run(){
while (bz.getCount()<10) {
synchronized (bz) {
if (bz.isFlag() == true) {
System.out.println("还有包子>>吃货吃包子");
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("没有包子>>第"+bz.getCount()+"次开始制作包子");
bz.setFlag(true);
System.out.println("包子做好了");
//唤醒等待线程(吃货)
bz.notify();
}
}
}
}
实例二:
// 需求: 两个线程 wait notify
// 线程1: 图片的加载 1% ~ 100% 图片的下载 1%~100%
// 线程2: 图片的显示 显示
// 同时开启线程: 显示之前 需要 加载完成
// 显示后 才能开始下载
package a_communication;
public class Demo2 {
public static void main(String[] args) {
String str = "这是用来做加锁的工具的, 此案例中什么意义都没有";
ShowTask show = new ShowTask(str);
LoadTask load = new LoadTask(str);
Thread loadThread = new Thread(load);
show.start();
loadThread.start();
}
}
// 显示的线程: 第一种线程创建的方式
class ShowTask extends Thread {
private String obj;
public ShowTask(String obj) {
this.obj = obj;
}
public void run() {
System.out.println("等待加载...");
synchronized (obj) {
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("显示图片");
// 显示后可以下载, 唤醒下载线程
synchronized (obj) {
obj.notify();
}
}
}
// 加载 + 下载的线程: 第二种创建线程的方式
class LoadTask implements Runnable {
private String obj;
public LoadTask(String obj) {
this.obj = obj;
}
@Override
public void run() {
System.out.println("开始加载");
for (int i = 0; i < 10; i++) {
System.out.println("正在加载: " + i + "%");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("加载完成, 等待显示..");
synchronized (obj) {
// 唤醒显示的线程 -> 显示的线程是进入到了就绪状态
obj.notify();
}
synchronized (obj) { // 等待显示完成
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("开始下载");
for (int i = 0; i < 10; i++) {
System.out.println("正在下载: " + i + "%");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("下载完成, 结束!");
}
}