java 多线程之wait、notify详解

转自:https://blog.csdn.net/zhujiangtaotaise/article/details/56281264

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();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
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("同步方法之外的方法");
    }

}
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
打印结果如下:

before wait
增加1条数据
增加2条数据
增加3条数据
增加4条数据
已发出notify通知
增加5条数据
增加6条数据
增加7条数据
增加8条数据
增加9条数据
增加10条数据
after wait
同步方法之外的方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
可以看出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());
    }
1
2
3
4
5
6
7
8
9
测试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();
1
2
3
4
5
6
7
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();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
结果如下:

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
1
2
3
4
5
可以看出线程C在调用sleep方法后并不会释放。

最后作点说明: 
每个锁对象都有两个队列,就绪队列以及阻塞队列。就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程。一个线程被唤醒后,才会进入就绪队列,以等待CPU调度。反之一个线程被wait后就会进入阻塞队列,等待下一次唤醒。 也就是说一个线程被wait后会进入阻塞队列,待调用了 notify或notifyAll之后,该线程就会进入就绪队列。

猜你喜欢

转载自blog.csdn.net/u012501054/article/details/88815765