Bootstrap

【JUC】LockSupport线程等待唤醒

LockSupport

线程等待唤醒机制

三种让线程等待和唤醒的方法

  • 方式一:使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程
  • 方式二:使用JUC包中的Condition的await()方法让线程等待,使用signal()方法唤醒线程
  • 方式三:LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程

在这里插入图片描述

Object类中的wait和notify方法实现线程等待和唤醒

  • wait和notify方法必须要在同步代码块或者同步方法里面(就是必须持有锁才能使用这两个方法),且成对出现使用
  • 先wait再notify才是合法的
  • 调用wait会交出我锁的控制权,让别人去争抢
public class LockSupportDemo {
    public static void main(String[] args) {
        Object objectLock = new Object();
       
        new Thread(() -> {
            synchronized (objectLock) {
                System.out.println(Thread.currentThread().getName() + "\t -----------come in");
                try {
                    // 调用wait()方法使当前线程释放锁并进入等待状态,直到其他线程调用notify()或notifyAll()唤醒它。
                    objectLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "\t -------被唤醒");
            }
        }, "t1").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            synchronized (objectLock) {
                // 调用notify()方法唤醒因调用wait()而处于等待状态的一个线程。
                objectLock.notify();
                System.out.println(Thread.currentThread().getName() + "\t -----------发出通知");
            }
        }, "t2").start();
    }
}

在这里插入图片描述

不用同步代码块

public class LockSupportDemo {
    public static void main(String[] args) {
        Object objectLock = new Object();
        /**
         * t1         -----------come in
         * t2         -----------发出通知
         * t1         -------被唤醒
         */
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t -----------come in");
            try {
                objectLock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "\t -------被唤醒");
        }, "t1").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            objectLock.notify();
            System.out.println(Thread.currentThread().getName() + "\t -----------发出通知");
        }, "t2").start();
    }
}

在这里插入图片描述

wait、notify顺序反过来

public class LockSupportDemo {
    public static void main(String[] args) {
        Object objectLock = new Object();
       
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (objectLock) {
                System.out.println(Thread.currentThread().getName() + "\t -----------come in");
                try {
                    objectLock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "\t -------被唤醒");
            }
        }, "t1").start();
     
        new Thread(() -> {
            synchronized (objectLock) {
                objectLock.notify();
                System.out.println(Thread.currentThread().getName() + "\t -----------发出通知");
            }
        }, "t2").start();
    }
}

卡死原因:wait之后,没有人唤醒我

在这里插入图片描述

Condition接口中的await和signal方法实现线程的等待和唤醒

  • Condition中的线程等待和唤醒方法,需要先持有锁,在锁块里面才能使用
  • 一定要先await后signal
public class LockSupportDemo {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
  
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "\t -----------come in");
                condition.await();
                System.out.println(Thread.currentThread().getName() + "\t -----------被唤醒");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "t1").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            lock.lock();
            try {
                condition.signal();
                System.out.println(Thread.currentThread().getName() + "\t -----------发出通知");
            } finally {
                lock.unlock();
            }
        }, "t2").start();
    }
}

在这里插入图片描述

模拟没有持有锁

public class LockSupportDemo {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
  
        new Thread(() -> {
            // lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "\t -----------come in");
                condition.await();
                System.out.println(Thread.currentThread().getName() + "\t -----------被唤醒");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // lock.unlock();
            }
        }, "t1").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            // lock.lock();
            try {
                condition.signal();
                System.out.println(Thread.currentThread().getName() + "\t -----------发出通知");
            } finally {
                // lock.unlock();
            }
        }, "t2").start();
    }
}

在这里插入图片描述

模拟没有先await后signal

public class LockSupportDemo {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
  
        new Thread(() -> {
            lock.lock();
            try {
               try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "\t -----------come in");
                condition.await();
                System.out.println(Thread.currentThread().getName() + "\t -----------被唤醒");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "t1").start();
    
        new Thread(() -> {
            lock.lock();
            try {
                condition.signal();
                System.out.println(Thread.currentThread().getName() + "\t -----------发出通知");
            } finally {
                lock.unlock();
            }
        }, "t2").start();
    }
}

发生阻塞

在这里插入图片描述

上述两种方法使用限制条件

  • 线程需要先获得并持有锁,必须在锁块(synchronized或lock)中
  • 必须要先等待后唤醒,线程才能够被唤醒

LockSupport类中的park等待和unpark唤醒

LockSupport 是什么
  • LockSupport是一个线程阻塞工具类

在这里插入图片描述

  • LockSupport是用来创建锁和其他同步类的基本线程阻塞原语,其中park()和unpack()的作用分别是阻塞线程和解除阻塞线程。调用park的时候,除非有许可证,不然线程就阻塞
  • LockSupport可以让线程再任意位置阻塞,阻塞后也有对应的唤醒方法
  • LockSupport是对等待唤醒机制的一种优化。LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程最多获取一个许可,重复调用unpark也不会积累凭证
  • 该类没有构造方法,所有的方法都是静态方法

在这里插入图片描述

  • 如果许可证可用,park方法立即返回,否则被阻止;如果没有许可,调用unpark可以获取一个许可
  • 参数传0的意思:“没有通行证,永远不放行”,LockSupport时调用Unsafe中的native代码。

在这里插入图片描述

在这里插入图片描述

  • 默认没有permit许可证,所以一开始调park()当前线程就会阻塞,直到别的线程给当前线程的发放permit,pack方法才会被唤醒。
  • 调用unpark(thread)方法后,就会给thread线程发放许可证,会自动唤醒park的线程,即之前阻塞中的LockSupport.park()方法会立即返回

在这里插入图片描述

  • 形象理解:线程阻塞需要消耗凭证(Permit),这个凭证最多只有1个
    • 当调用park时
      • 如果有凭证,则会直接消耗掉这个凭证然后正常退出
      • 如果没有凭证,则必须阻塞等待凭证可用
    • 当调用unpark时,它会增加一个凭证,但凭证最多只能有1个,累加无效。
主要方法
  • 阻塞: Peimit许可证默认没有不能放行,所以一开始调用park()方法当前线程会阻塞,直到别的线程给当前线程发放peimit,park方法才会被唤醒。
    • park/park(Object blocker):阻塞当前线程/阻塞传入的具体线程
  • 唤醒: 调用unpack(thread)方法后 就会将thread线程的许可证peimit发放,会自动唤醒park线程,即之前阻塞中的LockSupport.park()方法会立即返回。
    • unpark(Thread thread):唤醒处于阻塞状态的指定线程
代码测试
public class LockSupportDemo {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t -----------come in");
            // 等待
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + "\t ----------被唤醒");
        }, "t1");
        t1.start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(() -> {
            // 唤醒
            LockSupport.unpark(t1);
            System.out.println(Thread.currentThread().getName() + "\t ----------发出通知");
        }, "t2").start();
    }
}

不需要在锁块中才能执行等待、唤醒

在这里插入图片描述

public class LockSupportDemo {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
           try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "\t -----------come in");
            // 等待
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + "\t ----------被唤醒");
        }, "t1");
        t1.start();
     
        new Thread(() -> {
            // 唤醒
            LockSupport.unpark(t1);
            System.out.println(Thread.currentThread().getName() + "\t ----------发出通知");
        }, "t2").start();
    }
}

先用unpack发许可证,再pack,还是可以唤醒。类似高速公路的ETC,提前买好了通行证unpark,到闸机处直接抬起栏杆放行了,没有park拦截了。

在这里插入图片描述

【测试许可证是否可以积累】

public static void main(String[] args) {
    Thread t1 = new Thread(() -> {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "\t ----come in" + System.currentTimeMillis());
        LockSupport.park();
        LockSupport.park();
        System.out.println(Thread.currentThread().getName() + "\t ----被唤醒" + System.currentTimeMillis());
    }, "t1");
    t1.start();

    //暂停几秒钟线程
    //try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

    new Thread(() -> {
        LockSupport.unpark(t1);
        LockSupport.unpark(t1);
        LockSupport.unpark(t1);
        System.out.println(Thread.currentThread().getName() + "\t ----发出通知");
    }, "t2").start();
}

调用多次LockSupport.unpark(t1);也只有一个通行证,来到第二个park会被卡住

在这里插入图片描述

面试题

为什么LockSupport可以突破wait/notify的原有调用顺序?

  • 因为unpark获得了一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,故不会阻塞,先发放了凭证后续可以畅通无阻。

为什么唤醒两次后阻塞两次,但最终结果还会阻塞线程?

  • 因为凭证的数量最多为1,连续调用两次unpark和调用一次unpark效果一样,只会发一个凭证,而调用两次park却需要消费两个凭证,证不够,第二次不能放行。

文章说明

该文章是本人学习 尚硅谷 的学习笔记,文章中大部分内容来源于 尚硅谷 的视频尚硅谷JUC并发编程(对标阿里P6-P7),也有部分内容来自于自己的思考,发布文章是想帮助其他学习的人更方便地整理自己的笔记或者直接通过文章学习相关知识,如有侵权请联系删除,最后对 尚硅谷 的优质课程表示感谢。

;