1 简介

  目前,JAVA提供了三种线程等待唤醒的机制。

  1)synchronized + Object的wait()和notify()方法

  2)Lock的lock()方法和unlock()方法+await()和signal()方法

  3)LockSupport的park()和unpark()方法

 

2 示例:synchronized + Object的wait()和notify()方法

2.1 代码

public class LockTest1 {

    private static Object lo = new Object();

    public static void main(String[] args) {


        Thread t1 = new Thread(()->{
            synchronized (lo){
                System.out.println("aaaaaaaaaaa1");
                try {
                    lo.wait(); //让出锁,并等待notice
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("aaaaaaaaaaa2");
            }

        });

        Thread t2 = new Thread(()->{
            try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
            synchronized (lo){
                System.out.println("bbbbbbbbb1");
                try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
                System.out.println("bbbbbbbbb2");
                lo.notify(); //唤醒t1线程
                try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
                System.out.println("bbbbbbbbb3");
            }

        });
        t1.start();
        t2.start();

    }
}

 

2.2 执行结果

  t1线程线获得锁,然后调用wait方法让出锁,并等待。t2线程获取锁,然后调用notify()方法唤醒t1线程(注意,t2没有让出锁),等待t2执行完,t1获取锁继续执行

aaaaaaaaaaa1
bbbbbbbbb1
bbbbbbbbb2
bbbbbbbbb3
aaaaaaaaaaa2

 

2.3 注意事项

  1)wait和notice方法的调用必须在synchronize里面

  2)wait和notice方法一一对应

  3)wait方法需要先于notice方法执行

 

3 示例 Lock的lock()方法和unlock()方法+await()和signal()方法

3.1 代码

public class T9 {

        static Lock lock = new ReentrantLock();

        static Condition condition = lock.newCondition();

        public static void main(String[] args) throws InterruptedException {
            Thread t1 = new Thread(()->{
                System.out.println("aaaaaaaa1");
                try {
                    lock.lock();
                    //拿到锁后睡眠2秒,再打印
                    Thread.sleep(2000);
                    System.out.println("aaaaaaa2");
                    //打印完成后,调用await让出锁,等待唤醒
                    condition.await(); //让出锁,并等待signal
                }catch (Exception e){
                }finally {
                    //被唤醒后,打印
                    System.out.println("aaaaaaa3");
                    lock.unlock();
                }
            });

            Thread t2 = new Thread(()->{
                //睡1秒,保证t1拿到锁
                try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
                System.out.println("bbbbbbbb1");
                try {
                    lock.lock();
                    //获取锁后打印
                    System.out.println("bbbbbbbbb2");
                    //睡2秒后,唤醒t1
                    Thread.sleep(2000);
                    condition.signal(); //唤醒
                }catch (Exception e){
                }finally {
                    //唤醒t1后,睡1秒打印
                    try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
                    System.out.println("bbbbbbbbb3");
                    lock.unlock();
                }
            });
            t1.start();
            t2.start();
        }


}

 

 

3.2 执行结果

  t1先调用lock方法,获取锁,然后调用await()方法让出锁,并等待。

  t2调用lock方法获取锁,调用signal()方法唤醒t1线程(t2没有让出锁,还持有着锁)

  t2调用unlock方法让出锁,t1获取锁继续执行

 

aaaaaaaa1
bbbbbbbb1
aaaaaaa2
bbbbbbbbb2
bbbbbbbbb3
aaaaaaa3

 

 

 

 

3.3 注意事项

  1)await和signal方法的调用必须在lock和unlock之间

  2)await和signal方法一一对应

  3)await方法需要先于signal方法执行

 

4 LockSupport的park()和unpark()方法

4.1 代码

public class LockTest4 {

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(()->{
            System.out.println("aaaaaaaa1");
            try {
                LockSupport.park();
                System.out.println("aaaaaaa2");
            }catch (Exception e){

            }
        });

        Thread t2 = new Thread(()->{
            try {
                Thread.sleep(1000);
                System.out.println("bbbbbbbb1");
                Thread.sleep(3000);
                System.out.println("bbbbbbbbb2");
                LockSupport.unpark(t1);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t1.start();
        t2.start();
    }
}

 

 

4.2 执行结果

  t1线程执行,调用park方法,等待被唤醒。t2线程同时执行,调用unpark唤醒t1线程,t1线程继续执行。

  它和前面相比,没有用到锁,阻塞更少。

aaaaaaaa1
bbbbbbbb1
bbbbbbbbb2
aaaaaaa2

 

 

 

4.3 注意事项

  1)park和unpark的调用顺序随意

  2)调用park方法实际上是将该线程的permit设置为0,它会被阻塞,需要其它线程unpark它,就会把它的permit设置为1,它就会被唤醒

  3)permit的值只有0和1,所以,无论调用多少次unpark方法,permit的值都会是1,只能唤醒一次

  

4.3.1 示例1

public class LockTest5 {

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(()->{
            System.out.println("aaaaaaaa1");
            try {
                LockSupport.park();
                System.out.println("aaaaaaaaa2");
                LockSupport.park();
                System.out.println("aaaaaaa3");
            }catch (Exception e){

            }
        });

        Thread t2 = new Thread(()->{
            try {
                Thread.sleep(1000);
                System.out.println("bbbbbbbb1");
                Thread.sleep(3000);
                System.out.println("bbbbbbbbb2");
                LockSupport.unpark(t1);
                LockSupport.unpark(t1);
                System.out.println("bbbbbbbbb3");

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t1.start();
        t2.start();
    }
}

执行结果

  t1调用了两次park方法,t2虽然为t1调用了两次unpark方法,但是aaaaa3没有打印出来,t1一直阻塞着。因为t1最多只能拿一张通行证,有两个路卡,就只能过一个

aaaaaaaa1
bbbbbbbb1
bbbbbbbbb2
bbbbbbbbb3
aaaaaaaaa2

 

4.3.2 示例2

public class LockTest5 {

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(()->{
            System.out.println("aaaaaaaa1");
            try {
                LockSupport.park();
                System.out.println("aaaaaaaaa2");

                LockSupport.park();
                System.out.println("aaaaaaa3");
            }catch (Exception e){

            }
        });

        Thread t2 = new Thread(()->{
            try {
                Thread.sleep(1000);
                System.out.println("bbbbbbbb1");
                Thread.sleep(3000);
                System.out.println("bbbbbbbbb2");
                LockSupport.unpark(t1);
                Thread.sleep(2000);
                LockSupport.unpark(t1);
                System.out.println("bbbbbbbbb3");

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t1.start();
        t2.start();
    }
}

执行结果

   发现aaaaaa3打印出来了,因为两个unpark之间停顿了2秒,第一个unpark给t1发了一张通行证,过了第一个关卡,permit又变为0了,等了2秒,第二个unpark方法又给它发了一张通行证,过了第二个关卡。

aaaaaaaa1
bbbbbbbb1
bbbbbbbbb2
aaaaaaaaa2
bbbbbbbbb3
aaaaaaa3

Process finished with exit code 0

 

5 几种等待的特点及区别

 

  是否是否锁 是否传入等待时间 唤醒 方法调用前提 调用顺序 其它说明
Thread.sleep 必须 时间到自动醒      
object.wait+notify 可传可不传

传时间:时间到自动醒

不传时间:调用notify方法唤醒

必须在synchronized中

wait在前

notify在后

 
condition.await+signal 可传可不传

传时间:时间到自动醒

不传时间:调用signal方法唤醒

必须在Lock unlock之间使用

await在前

signal在后

 
LockSupport.park+unpark 不用传 调用unpark方法唤醒   park和unpark顺序随意

1)调用park方法实际上是

将该线程的permit设置为0,

它会被阻塞,需要其它线程

unpark它就会把它的permit设置为1,

它就会被唤醒

2)permit的值只有0和1,所以,

无论调用多少次unpark方法,

permit的值都会是1,只能唤醒一次

 

6 示例证明LockSupport不会释放锁

public class Test1 {

    //测试park方法是否会释放锁

    public static void main(String[] args) {

        Object o = new Object();

        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "执行");
            synchronized (o){
                System.out.println(Thread.currentThread().getName() + "拿到锁");
                LockSupport.park();
                System.out.println(Thread.currentThread().getName() + "执行结束");
            }
        }, "线程1");

        Thread t2 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "执行");
            synchronized (o){
                System.out.println(Thread.currentThread().getName() + "拿到锁");
                LockSupport.unpark(t1);
                System.out.println(Thread.currentThread().getName() + "执行结束");
            }
        }, "线程2");

        t1.start();
        try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); }
        t2.start();
    }
}

 

执行结果,一直阻塞着,没有执行完

在线程1获取锁后,调用park方法进入等待状态,线程2没有拿到锁,一致处于阻塞状态,说明线程1进入等待时,没有释放锁

线程1执行
线程1拿到锁
线程2执行

 

7 示例(证明object.wait会释放锁)

public class Test2 {

    //测试object.wait方法是否会释放锁

    public static void main(String[] args) {

        Object o = new Object();

        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "执行");
            synchronized (o){
                System.out.println(Thread.currentThread().getName() + "拿到锁");
                try { o.wait(); } catch (InterruptedException e) { e.printStackTrace(); }
                System.out.println(Thread.currentThread().getName() + "执行结束");
            }
        }, "线程1");

        Thread t2 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "执行");
            synchronized (o){
                System.out.println(Thread.currentThread().getName() + "拿到锁");
                o.notify();
                System.out.println(Thread.currentThread().getName() + "执行结束");
            }
        }, "线程2");

        t1.start();
        try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); }
        t2.start();
    }

执行结果,在线程1获取锁后,调用wait方法进入等待状态,线程2拿到锁了,说明线程1进入等待时,释放锁了

线程1执行
线程1拿到锁
线程2执行
线程2拿到锁
线程2执行结束
线程1执行结束

 

8 线程状态迁移图