wait()、notify()和notifyAll()是Object类中的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
从这三个方法的文字描述可以知道以下几点信息:
1)wait()、notify()和notifyAll()方法是本地方法,并且为final方法,无法被重写。
2)调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的monitor(即锁)
3)调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程;
4)调用notifyAll()方法能够唤醒所有正在等待这个对象的monitor的线程;
有朋友可能会有疑问:为何这三个不是Thread类声明中的方法,而是Object类中声明的方法(当然由于Thread类继承了Object类,所以Thread也可以调用者三个方法)?其实这个问题很简单,由于每个对象都拥有monitor(即锁),所以让当前线程等待某个对象的锁,当然应该通过这个对象来操作了。而不是用当前线程来操作,因为当前线程可能会等待多个线程的锁,如果通过线程来操作,就非常复杂了。
上面已经提到,如果调用某个对象的wait()方法,当前线程必须拥有这个对象的monitor(即锁),因此调用wait()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。
调用某个对象的wait()方法,相当于让当前线程交出此对象的monitor,然后进入等待状态,等待后续再次获得此对象的锁(Thread类中的sleep方法使当前线程暂停执行一段时间,从而让其他线程有机会继续执行,但它并不释放对象锁);
notify()方法能够唤醒一个正在等待该对象的monitor的线程,当有多个线程都在等待该对象的monitor的话,则只能唤醒其中一个线程,具体唤醒哪个线程则不得而知。
同样地,调用某个对象的notify()方法,当前线程也必须拥有这个对象的monitor,因此调用notify()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。
nofityAll()方法能够唤醒所有正在等待该对象的monitor的线程,这一点与notify()方法是不同的。
这里要注意一点:notify()和notifyAll()方法只是唤醒等待该对象的monitor的线程,并不决定哪个线程能够获取到monitor。
package com.bjsxt.base.conn008;
import java.util.ArrayList;
import java.util.List;
public class ListAdd1 {
private volatile static List list = new ArrayList();
public void add(){
list.add("bjsxt");
}
public int size(){
return list.size();
}
public static void main(String[] args) {
final ListAdd1 list1 = new ListAdd1();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
for(int i = 0; i <10; i++){
list1.add();
System.out.println("当前线程:" + Thread.currentThread().getName() + "添加了一个元素..");
Thread.sleep(500);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
while(true){
if(list1.size() == 5){
System.out.println("当前线程收到通知:" + Thread.currentThread().getName() + " list size = 5 线程停止..");
throw new RuntimeException();
}
}
}
}, "t2");
//t1 t2 谁在前无所谓,因为是异步的
t1.start();
t2.start();
}
}
结果为 只要是 t2线程 list size = 5 线程就停止了抛出异常
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程收到通知:t2 list size = 5 线程停止..
Exception in thread "t2" java.lang.RuntimeException
at com.bjsxt.base.conn008.ListAdd1$2.run(ListAdd1.java:42)
at java.lang.Thread.run(Thread.java:722)
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
方法wait() 的作用是使当前执行代码的线程进行等待,wait() 方法是object 类的方法,该方法用来将当前线程置入“预执行队列中”,并且在wait() 所在的代码行处停止执行,直到接到通知或被中断为止,在调用wait() 之前,线程必须获得该对象的对象级别锁,即只能是在同步方法或同步块中调用wait() 方法,在执行wait() 方法后,当前线程释放锁,在wait() 返回前,线程与其他线程竞争重新获得所,如果调用wait() 时没有持有适当的锁,则跑出异常,IIIegalMonitorStateException 它是RuntimeException 的一个子类,因此,不需要try-catch 语句进行捕捉异常
方法notify() 也要在同步方法或者同步块中调用,即在调用前,线程也必须获得该对象的级别锁,如果调用notify() 时没有持有适当的锁,也会抛出IIIegalMonitorStateException 该方法用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随机挑选一个呈wait 状态的线程,对其发出通知notify ,并使它等待获取该对象的对象锁,呈wait状态的线程也并不能马上获取该对象锁,要等到执行notify() 方法的线程将程序执行完,也就是退出synchronized 代码块以后,当前线程才会释放锁,而呈wait 状态所在的线程才可以获取该对象锁,当第一个获得了该对象的wait 线程运行完毕以后,它会释放掉该对象的锁,此时如果没有再次使用notify 语句,则即便对该对象已经空闲,其他wait 状态等待的线程由于没有得到该对象的通知,还会继续阻塞在wait 状态,直到这个对象发出一个notify或者notifyAll .
package test;
public class Test1 {
public static void main(String[] args) {
try {
String newString = new String("");
newString.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Exception in thread "main" java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:485)
at test.Test1.main(Test1.java:7)
出现异常的原因是没有“对象监视器”,也就是没有加同步锁
package test;
public class Test2 {
public static void main(String[] args) {
try {
String lock = new String();
System.out.println("syn上面");
synchronized (lock) {
System.out.println("syn第一行");
lock.wait();
System.out.println("wait下的代码!");
}
System.out.println("syn下面的代码");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结果为
syn上面
syn第一行
把上述的代码修改为wait/notify 的方式
package com.bjsxt.base.conn008;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
* @author alienware
*
*/
public class ListAdd2 {
private volatile static List list = new ArrayList();
public void add(){
list.add("bjsxt");
}
public int size(){
return list.size();
}
public static void main(String[] args) {
final ListAdd2 list2 = new ListAdd2();
final Object lock = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
//t1线程拿着锁,就不释放了,直到线程运行完毕以后才会运行t2
synchronized (lock) {
System.out.println("t1启动..");
for(int i = 0; i <10; i++){
list2.add();
System.out.println("当前线程:" + Thread.currentThread().getName() + "添加了一个元素..");
Thread.sleep(500);
if(list2.size() == 5){
System.out.println("已经发出通知..");
lock.notify();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
System.out.println("t2启动..");
if(list2.size() != 5){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("当前线程:" + Thread.currentThread().getName() + "收到通知线程停止..");
throw new RuntimeException();
}
}
}, "t2");
//t2 线程一定是在前面的,程序运行到lock.wait(); 由于开始进来的时候,list 的一定是不等于5的,所以就一直等待,由于wait 等待的时候是释放锁的,所以锁释放了开始运行t1线程
t2.start();
t1.start();
}
}
结果是
t2启动..
t1启动..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
已经发出通知..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t1添加了一个元素..
当前线程:t2收到通知线程停止..
Exception in thread "t2" java.lang.RuntimeException
at com.bjsxt.base.conn008.ListAdd2$2.run(ListAdd2.java:60)
at java.lang.Thread.run(Thread.java:722)
但是这样写有一个弊端,不能做到实时通知的效果应该改为下面这个样子
package com.bjsxt.base.conn008;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
* @author alienware
*
*/
public class ListAdd2 {
private volatile static List list = new ArrayList();
//是线程包当中的类加上了这个就可以做到实时通知的目的,但是这个类不能与synchronized 合在一起用
final static CountDownLatch countDownLatch =new CountDownLatch(2);
public void add(){
list.add("bjsxt");
}
public int size(){
return list.size();
}
public static void main(String[] args) {
final ListAdd2 list2 = new ListAdd2();
final Object lock = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
//CountDownLatch不能与synchronized 合在一起用
// synchronized (lock) {
System.out.println("t1启动..");
for(int i = 0; i <10; i++){
list2.add();
System.out.println("当前线程:" + Thread.currentThread().getName() + "添加了一个元素..");
Thread.sleep(500);
if(list2.size() == 5){
System.out.println("已经发出通知..");
// lock.notify();
countDownLatch.countDown();
countDownLatch.countDown();
}
// }
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
////CountDownLatch不能与synchronized 合在一起用
// synchronized (lock) {
System.out.println("t2启动..");
if(list2.size() != 5){
try {
// lock.wait();
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("当前线程:" + Thread.currentThread().getName() + "收到通知线程停止..");
throw new RuntimeException();
// }
}
}, "t2");
t2.start();
t1.start();
}
}
源码 notifyOne
package extthread;
import service.Service;
public class NotifyThread extends Thread {
private Object lock;
public NotifyThread(Object lock) {
super();
this.lock = lock;
}
@Override
public void run() {
synchronized (lock) {
lock.notify();
lock.notify();
lock.notify();
lock.notify();
lock.notify();
lock.notify();
lock.notify();
lock.notify();
lock.notify();
}
}
}
一共是ThreadA ThreadB ThreadC 三个类的代码是完全一样的
package extthread;
import service.Service;
public class ThreadA extends Thread {
private Object lock;
public ThreadA(Object lock) {
super();
this.lock = lock;
}
@Override
public void run() {
Service service = new Service();
service.testMethod(lock);
}
}
package service;
public class Service {
public void testMethod(Object lock) {
try {
synchronized (lock) {
System.out.println("begin wait() ThreadName="
+ Thread.currentThread().getName());
lock.wait();
//每一个线程进来以后,锁都释放了,ThreadA ThreadB ThreadC 三种线程
System.out.println(" end wait() ThreadName="
+ Thread.currentThread().getName());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package test;
import extthread.NotifyThread;
import extthread.ThreadA;
import extthread.ThreadB;
import extthread.ThreadC;
public class Test {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
ThreadA a = new ThreadA(lock);
a.start();
ThreadB b = new ThreadB(lock);
b.start();
ThreadC c = new ThreadC(lock);
c.start();
Thread.sleep(1000);
NotifyThread notifyThread = new NotifyThread(lock);
notifyThread.start();
}
}
随机会唤醒一个线程
begin wait() ThreadName=Thread-1
begin wait() ThreadName=Thread-2
begin wait() ThreadName=Thread-0
end wait() ThreadName=Thread-1
end wait() ThreadName=Thread-0
end wait() ThreadName=Thread-2
多次调用notify() 方法唤醒了全部waiting 中的线程
package extthread;
public class NotifyThread extends Thread {
private Object lock;
public NotifyThread(Object lock) {
super();
this.lock = lock;
}
@Override
public void run() {
synchronized (lock) {
lock.notifyAll();
}
}
}
一次性唤醒所有线程
begin wait() ThreadName=Thread-0
begin wait() ThreadName=Thread-2
begin wait() ThreadName=Thread-1
end wait() ThreadName=Thread-1
end wait() ThreadName=Thread-2
end wait() ThreadName=Thread-0
是等待某一个时间是否有线程对锁进行唤醒,如果超过这个时间则自动唤醒
源码 waitHasParamMethod
package myrunnable;
public class MyRunnable {
static private Object lock = new Object();
static private Runnable runnable1 = new Runnable() {
@Override
public void run() {
try {
synchronized (lock) {
System.out.println("wait begin timer="
+ System.currentTimeMillis());
lock.wait(5000);
System.out.println("wait end timer="
+ System.currentTimeMillis());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
static private Runnable runnable2 = new Runnable() {
@Override
public void run() {
synchronized (lock) {
System.out.println("notify begin timer="
+ System.currentTimeMillis());
lock.notify();
System.out.println("notify end timer="
+ System.currentTimeMillis());
}
}
};
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(runnable1);
t1.start();
Thread.sleep(3000);
Thread t2 = new Thread(runnable2);
t2.start();
}
}
wait begin timer=1533888100845
notify begin timer=1533888103846
notify end timer=1533888103847
wait end timer=1533888103847
源码 firstNotify
package test;
public class MyRun {
private String lock = new String("");
private boolean isFirstRunB = false;
private Runnable runnableA = new Runnable() {
@Override
public void run() {
try {
synchronized (lock) {
while (isFirstRunB == false) {
System.out.println("begin wait");
lock.wait();
System.out.println("end wait");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
private Runnable runnableB = new Runnable() {
@Override
public void run() {
synchronized (lock) {
System.out.println("begin notify");
lock.notify();
System.out.println("end notify");
isFirstRunB = true;
}
}
};
public static void main(String[] args) throws InterruptedException {
MyRun run = new MyRun();
Thread a = new Thread(run.runnableA);
a.start();
Thread.sleep(100);//把这个注释掉永远不会被通知
Thread b = new Thread(run.runnableB);
b.start();
}
}
如果先通知了,则wait 方法也就没有必要执行了