正在找实习的我真的是难过,没什么公司通知我去面试,唉... 闲着没事学习学习多线程方面知识。
PS:此笔记根据《java多线程编程核心技术》学习而来,新手,有错误的地方希望谅解然后多多指出!!!
1.什么是等待通知机制呢?
举个例子,我们去饭店首先要点菜,然后服务员需要将点的菜告诉厨师,厨师才能对应着菜单进行做饭,这时候呢服务员不可能一直问厨师这个菜做好没那个菜做好没,这样效率会非常低下。于是服务员在这个时候可以去干其他事情,比如调戏下男服务员一起去拉个翔啥的…
当厨师做好菜时,才会通知服务员,这桌的菜做好啦,你可以上给顾客了,于是服务员就将菜呈上给我们,这就是等待通知机制。
2.等待/通知机制的实现
方法wait()的作用是使当前执行代码的线程进行等待。在调用wait()之前,线程必须获得该对象的对象级别锁,即只能在同步方法或同步块中调用wait方法。如果调用wait时没有持有适当的锁,就会抛出IllegalMonitorStateException,它是RuntimeException的一个子类,因此不需要try-catch进行捕捉。
方法notify()作用是用来通知等待该对象的对象锁的其他线程,如果有多个线程等待,则有线程规划器随机挑选一个在wait的线程。同样需要在同步方法和同步块中调用,在调用之前,也要获得该对象的对象级别锁。如果调用notify时没持有适当的锁,也会抛出IllegalMonitorStateException。需要注意的是,执行notif方法的线程要将程序执行完,也就是推出synchronized代码块后,当前线程才会释放锁,wait状态的线程才能获得该对象锁。
总之一句话:wait使线程停止,notify使停止的线程继续运行。
光说不做可不行,实战练习
创建test2项目
package com.zz.test2;
import java.util.ArrayList;
public class MyList {
private static ArrayList list = new ArrayList();
public static void add() {
list.add("Anything");
}
public static int size() {
return list.size();
}
}
创建Thread1类
public class Thread1 extends Thread{
private Object lock;
public Thread1(Object lock) {
super();
this.lock = lock;
}
@Override
public void run() {
try {
synchronized (lock) {
if(MyList.size() != 5) {
System.out.println("wait begin" + System.currentTimeMillis());
lock.wait(); //list长度不等于5就进行等待
System.out.println("wait end" + System.currentTimeMillis());
}
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
创建Thread2类
public class Thread2 extends Thread{
private Object lock;
public Thread2(Object lock) {
super();
this.lock = lock;
}
@Override
public void run() {
try {
synchronized (lock) {
for(int i = 0; i < 10; i++) {
MyList.add();
if(MyList.size() == 5) {
lock.notify(); //list长度等于5就会发出通知,但同步块依旧运行,只有运行完毕才会释放对象锁
System.out.println("已发送通知");
}
System.out.println("加添了" + (i + 1) + "个元素");
Thread.sleep(1000);
}
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
最后Run一个
public class Run {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
Thread1 a = new Thread1(lock);
a.start();
Thread.sleep(50);
Thread2 b = new Thread2(lock);
b.start();
}
}
运行结果:
wait begin1553767130564
加添了1个元素
加添了2个元素
加添了3个元素
加添了4个元素
已发送通知
加添了5个元素
加添了6个元素
加添了7个元素
加添了8个元素
加添了9个元素
加添了10个元素
wait end1553767140663
wait end在最后输出,也说明notify()方法在执行之后并不立即释放锁
wait方法可以使方法的线程释放当前共享资源的锁,从运行状态退出进入等待队列,直到被再次唤醒。
notify方法可以随机唤醒一个在等待队列的同一个共享资源的 “ 一个 ” 等待线程。
notifyAll方法可以唤醒在等待队列的同一个共享资源的 “ 全部 ” 等待线程。此时优先级最高的线程先执行,也有可能是随机一个,这要看JVM虚拟机的实现。
等待wait的条件发生变化
在使用wait/notify模式时,需要注意一个事项,那就是等待的条件如果发生混乱,容易造成程序逻辑的混乱,所以咱们创建个项目来看是怎么个混乱法
Add类:
package com.zz.waitOld;
public class Add {
private String lock;
public Add(String lock) {
super();
this.lock = lock;
}
public void add() {
synchronized (lock) {
ValueObjcet.list.add("Anything");
lock.notifyAll();
}
}
}
Subtract类:
package com.zz.waitOld;
public class Subtract {
private String lock;
public Subtract(String lock) {
super();
this.lock = lock;
}
public void subtract() {
try {
synchronized (lock) {
if (ValueObjcet.list.size() == 0) {
System.out.println(" wait begin Name = " + Thread.currentThread().getName());
lock.wait();
System.out.println(" wait end Name = " + Thread.currentThread().getName());
}
ValueObjcet.list.remove(0);
System.out.println("list size=" + ValueObjcet.list.size());
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
ThreadAdd类:
package com.zz.waitOld;
public class ThreadAdd extends Thread {
private Add p;
public ThreadAdd(Add p) {
super();
this.p = p;
}
public void run() {
p.add();
}
}
ThreadSubject类:
package com.zz.waitOld;
public class ThreadSubject extends Thread {
private Subtract p;
public ThreadSubject(Subtract p) {
super();
this.p = p;
}
public void run() {
p.subtract();
}
}
LIst类:
package com.zz.waitOld;
import java.util.ArrayList;
public class ValueObjcet {
public static ArrayList list = new ArrayList();
}
最后一个当然是Run类啦:
package com.zz.waitOld;
public class Run {
public static void main(String[] args) throws InterruptedException {
String lock = new String("");
Add add = new Add(lock);
Subtract subtract = new Subtract(lock);
ThreadSubject ts1 = new ThreadSubject(subtract);
ts1.setName("subtract1Thread");
ts1.start();
ThreadSubject ts2 = new ThreadSubject(subtract);
ts2.setName("subtract2Thread");
ts2.start();
Thread.sleep(1000);
ThreadAdd addThread = new ThreadAdd(add);
addThread.setName("addThread");
addThread.start();
}
}
运行结果:
wait begin Name = subtract1Thread
wait begin Name = subtract2Thread
wait end Name = subtract2Thread
Exception in thread "subtract1Thread" list size=0
wait end Name = subtract1Thread
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.rangeCheck(Unknown Source)
at java.util.ArrayList.remove(Unknown Source)
at com.zz.waitOld.Subtract.subtract(Subtract.java:19)
at com.zz.waitOld.ThreadSubject.run(ThreadSubject.java:13)
这是由于list经过一次删除后,已经无数据,但是先第二次remover()的线程操作后,造成了索引溢出的异常,所以只需改动一个地方Subtract类,使程序逻辑改变一下即可
Subtract类:
package com.zz.waitOld;
public class Subtract {
private String lock;
public Subtract(String lock) {
super();
this.lock = lock;
}
public void subtract() {
try {
synchronized (lock) {
while (ValueObjcet.list.size() == 0) { //仅此一个地方改动
System.out.println(" wait begin Name = " + Thread.currentThread().getName());
lock.wait();
System.out.println(" wait end Name = " + Thread.currentThread().getName());
}
ValueObjcet.list.remove(0);
System.out.println("list size=" + ValueObjcet.list.size());
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
运行结果我就不粘贴了,手动敲代码运行起来才是最爽的!!!
话说为什么我觉得这个编辑器怎么这么难用