wait 和 notify以及notifyAll
(1)、方法介绍
## 1.wait、notify以及notifyAll都是Object对象的方法,他们必须在被 synchronized 同步的方法或代码块中调用,否则会报错。
## 2. 调用wait方法会使该线程进入等待状态,并且会释放被同步对象的锁。
## 3. notify操作可以唤醒一个因执行wait而处于阻塞状态的线程,使其进入就绪状态,被唤醒的线程会去尝试着获取对象锁,然后执行wait之后的代码。如果发出notify操作时,没有线程处于阻塞状态,那么该命令会忽略。注意执行notify并不会马上释放对象锁,会等到执行完该同步方法或同步代码块后才释放,下面会有例子来证明。
notify方法可以随机唤醒等待队列中等待同一共享资源的“一个”线程,使其退出等待队列进入可运行状态。
4. notifyAll方法可以唤醒等待队列中等待同一共享资源的“全部”线程从等待状态退出,进入可运行状态。此时,优先级最高的那个线程优先执行,但也有可能是随机执行,这取决于JVM虚拟机的实现。
测试在调用notify方法之后并不会马上释放对象锁,而是在执行完同步方法或同步方法块的时候才会释放。
代码如下:
public class MyList {
private static List<String> list = new ArrayList<String>();
public static void add(){
list.add("sth");
}
public static int getSize(){
return list.size();
}
}
main方法以及两个线程类如下:
public class Test {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
ThreadA ta = new ThreadA(lock);
Thread tta = new Thread(ta);
tta.start();
Thread.sleep(50);
ThreadB tb = new ThreadB(lock);
Thread ttb = new Thread(tb);
ttb.start();
}
}
class ThreadA implements Runnable{
private Object mLock;
public ThreadA(Object lock){
mLock = lock;
}
public void run() {
synchronized (mLock) {
if(MyList.getSize() != 5){
try {
System.out.println("before wait");
mLock.wait();
System.out.println("after wait");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class ThreadB implements Runnable{
private Object mLock;
public ThreadB(Object lock){
mLock = lock;
}
public void run() {
synchronized (mLock) {
for(int i = 0; i< 10; i++){
MyList.add();
if(MyList.getSize() == 5){
mLock.notify();
System.out.println("已发出notify通知");
}
System.out.println("增加"+(i+1)+"条数据");
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("同步方法之外的方法");
}
}
打印结果如下:
before wait
增加1条数据
增加2条数据
增加3条数据
增加4条数据
已发出notify通知
增加5条数据
增加6条数据
增加7条数据
增加8条数据
增加9条数据
增加10条数据
after wait
同步方法之外的方法
可以看出2点:
1.执行wait方法后会立马释放对象锁
2.执行notify不会立马释放对象锁,需等该同步方法或同步块执行完。注意是同步的内容执行完,而不是该线程的run方法执行完,从结果最后2句可以看出来。
最后说下 wait和sleep的区别,这也是面试经常面到的问题。
1.sleep是Thread类的方法而wait是Object类的方法。
2.sleep不会立马释放对象锁,而wait会释放。
写个小栗子来证明结论2:
在MyList类中增加一个方法:
public synchronized void doSth(){
System.out.println("Thrad name : "+Thread.currentThread().getName()+" , begain doSth time : "+System.currentTimeMillis());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thrad name : "+Thread.currentThread().getName()+" , end doSth time : "+System.currentTimeMillis());
}
测试main方法
MyList myList = new MyList();
ThreadC tc = new ThreadC(myList);
tc.setName("C");
ThreadD td = new ThreadD(myList);
td.setName("D");
tc.start();
td.start();
ThreadC和ThreadD类如下:
class ThreadC extends Thread{
private MyList myList;
public ThreadC(MyList mlist){
myList = mlist;
}
@Override
public void run() {
myList.doSth();
}
}
class ThreadD extends Thread{
private MyList myList;
public ThreadD(MyList mlist){
myList = mlist;
}
@Override
public void run() {
myList.doSth();
}
}
结果如下:
Thrad name : C , begain doSth time : 1487647524673
Thrad name : C , end doSth time : 1487647527674
Thrad name : D , begain doSth time : 1487647527674
Thrad name : D , end doSth time : 1487647530674
可以看出线程C在调用sleep方法后并不会释放。
最后作点说明:
每个锁对象都有两个队列,就绪队列以及阻塞队列。就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程。一个线程被唤醒后,才会进入就绪队列,以等待CPU调度。反之一个线程被wait后就会进入阻塞队列,等待下一次唤醒。 也就是说一个线程被wait后会进入阻塞队列,待调用了 notify或notifyAll之后,该线程就会进入就绪队列。