一、wait、notify、notifAll
所有Object都有这三个方法。
wait :当前线程等待锁(放弃当前线程持有的锁)
notify:随机通知等待此锁的线程准备获取锁
notifyALL:释放锁并通知所有等待此锁的线程
整个等待与通知的过程,类似餐馆上菜,服务员等待菜,厨师(线程)做好菜(锁)通知服务员(线程)。
在等待通知的过程中,必须要有锁的存在,也就是说必须要持有锁,才能进行等待与通知,如果没有则会出现异常。
如下:
public static voidtest(TestWait lock){synchronized(lock){
System.out.println(Thread.currentThread().getName()+":"+"等菜");try{
lock.wait();
System.out.println(Thread.currentThread().getName()+":"+"上完了,再点菜");
lock.notify();
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}public static voidtest2(TestWait lock){synchronized(lock){
System.out.println(Thread.currentThread().getName()+":"+"做好了");try{
lock.notify();
System.out.println(Thread.currentThread().getName()+":"+"等客人");
lock.wait();
System.out.println("厨师发现没有原料了,下班");
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}public static void main(String[] args) throwsInterruptedException {
TestWait testWait= newTestWait();
Thread t= new Thread(newRunnable() {
@Overridepublic voidrun() {
test(testWait);
}
});
Thread t2= new Thread(newRunnable() {
@Overridepublic voidrun() {
test2(testWait);
}
});
t.start();
Thread.sleep(1000);
t2.start();
}
输出:
由结果看出,线程1与线程2通过wait、notify 实现了通信(前提是锁一致)
notify针对的是随机通知一个线程,如果争夺锁的线程太多,而又一个线程的优先级打个比方较低,有可能会一致都争夺不到锁。
上面的这种可能性较低,但是如果调用notify次数不够怎么办,假设有A,B 2个线程调用wait方法放弃锁,然后等待,线程C 拿到锁后调用notify,那么线程A与线程B只有一个能获取到锁,另一个只能继续等待通知,等待一个永远获取不到的锁。
这时候就必须通过notifyAll 来通知A和B,这样把wait状态的他们解放出来,加入到争夺锁的队列中去。
还有一种情况就是通知过早,如果线程Await,线程Bnotify,锁为C,如果线程B先通知了,那么A 就永远在等待C锁了。
如果遇到感觉可能会出现这种情况,最好加上一个标识用来判断是否有必要等待。
private static boolean isWait = true;public static voidtest(TestWait lock){synchronized(lock){try{//还要不要等菜
if(isWait){
lock.wait();
System.out.println(Thread.currentThread().getName()+":"+"等菜");
}
System.out.println(Thread.currentThread().getName()+":"+"上完了");
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}public static voidtest2(TestWait lock){synchronized(lock){
System.out.println(Thread.currentThread().getName()+":"+"做好了");
lock.notify();
isWait= false;
}
}public static void main(String[] args) throwsInterruptedException {
TestWait testWait= newTestWait();
Thread t= new Thread(newRunnable() {
@Overridepublic voidrun() {
test(testWait);
}
});
Thread t2= new Thread(newRunnable() {
@Overridepublic voidrun() {
test2(testWait);
}
});
Thread A= newThread(t);
t2.start();
Thread.sleep(2000);
A.start();
}
输出:
结果看出,服务员不要再等待了,菜已经做好,直接上就行了。
wait等待造成状态改变
多个现在执行wait等待后,在某些条件改变后,有可能会出现意想不到的异常,如:
private static boolean isWait = true;private static List list = newArrayList();public static voidtest(TestWait lock){synchronized(lock){try{//还要不要等菜
if(isWait){
if(list.size() == 0){
lock.wait();
System.out.println(Thread.currentThread().getName()+":"+"等菜");
}
}
list.remove(0);
System.out.println(Thread.currentThread().getName()+":"+"上完了");
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}public static voidtest2(TestWait lock){synchronized(lock){
System.out.println(Thread.currentThread().getName()+":"+"做好了");
list.add("菜");
lock.notifyAll();
isWait= false;
}
}public static void main(String[] args) throwsInterruptedException {
TestWait testWait= newTestWait();
Thread t= new Thread(newRunnable() {
@Overridepublic voidrun() {
test(testWait);
}
});
Thread t2= new Thread(newRunnable() {
@Overridepublic voidrun() {
test2(testWait);
}
});
Thread A= newThread(t);
A.start();
Thread B= newThread(t);
B.start();
Thread.sleep(2000);
t2.start();
}
输出:
结果是越界了,因为厨师只做了一道菜。
解决方法:轮询
while (list.size() == 0){
System.out.println(Thread.currentThread().getName()+":"+"等菜");
lock.wait();
}
输出:
第二个服务员继续等菜。(此事程序未结束)
生存者/消费者
private String str = "";classP {public voidsetV(Object o){synchronized(o){if(!str.equals("")){try{
o.wait();
}catch(InterruptedException e) {
e.printStackTrace();
}
}
str= Math.random() + "";
System.out.println("Set的值:"+str);
o.notifyAll();
}
}
}classC {public voidgetV(Object o){synchronized(o){if(str.equals("")){try{
o.wait();
}catch(InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Get的值:"+str);
str= "";
o.notifyAll();
}
}
}public static voidmain(String[] args){
Object lock= newObject();
TestPC pc= newTestPC();
P p= pc.newP();
C c= pc.newC();
Thread t= new Thread(newRunnable() {
@Overridepublic voidrun() {while (true){
c.getV(lock);
}
}
});
Thread t2= new Thread(newRunnable() {
@Overridepublic voidrun() {while (true){
p.setV(lock);
}
}
});
t.start();
t2.start();
}
输出:
上面是1对1,所以使用notify也是可以的,但是如果为多生产和多消费的话,就必须使用notifyAll 了。
还有如果对应的是1生产多消费,那么除了使用notifyAll 还要使用轮询机制,也就是while,类似上面的2个服务员一个厨师的例子。