LockSupport之等待唤醒机制

发布于:2023-01-05 ⋅ 阅读:(203) ⋅ 点赞:(0)

LockSupport是什么

用于创建锁和其他同步类的基本线程阻塞原语。

核心就是park()unpark()方法

  • park()方法是阻塞线程

  • unpark()方法是解除阻塞线程

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

  1. 使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程

  2. 使用JUC包中Conditionawait()方法让线程等待,使用signal()方法唤醒线程

  3. LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程

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

  • wait和notify必须加同步块或者方法里面
  • 先wait后notify
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 {
                    objectLock.wait();//等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+"\t"+"---被唤醒了");
        },"t1").start();

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

        new Thread(() -> {
            synchronized (objectLock) {
                objectLock.notify();//唤醒它
                System.out.println(Thread.currentThread().getName()+"\t ---发出通知");
            }
        },"t2").start();
    }
}
//t1   ---- come in
//t2   ---发出通知
//t1  ---被唤醒了

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

  • Condition中的线程等待和唤醒方法,需要先获取锁
  • 先await后signal
public class ConditionDemo {
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        new Thread(() -> {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " come in");
                condition.await();
                System.out.println(Thread.currentThread().getName() + " 被唤醒");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }, "t1").start();

        new Thread(() -> {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " 发出通知");
                condition.signal();
            } finally {
                lock.unlock();
            }
        }, "t2").start();

    }
}


t1 come in
t2 发出通知
t1 被唤醒

 Object和Condition使用的限制条件

  • 线程先要获得并持有锁,必须在锁块(synchronized或lock)中

  • 必须要先等待后唤醒,线程才能够被唤醒

③LockSupport类中的park等待和unpark唤醒 

Lock Support是用来创建锁和其他同步类的基本线程阻塞原语。

LockSupport类使用了一种名为Permit(许可) 的概念来做到阻塞和唤醒线程的功能, 每个线程都有一个许可(permit),与 Semaphore 不同的是,许可的累加上限是1

 Lock Support提供park()unpark()方法实现阻塞线程解除线程阻塞的过程

Lock Support和每个使用它的线程都有一个许可(permit) 关联。
每个线程都有一个相关的permit, permit最多只有一个, 重复调用unpark也不会积累凭证。

  • permit默认是零,所以一开始调用park()方法,当前线程就会阻塞,直到别的线程将当前线程的permit设置为1时,park方法会被唤醒,
    然后会将permit再次设置为零并返回。
  • 调用unpark(thread)方法后,就会将thread线程的许可permit设置成1
  • (注意多次调用unpark方法,不会累加,permit值还是1)会自动唤醒thread线程,即之前阻塞中的LockSupport.park()方法会立即返回。

public class ParkDemo {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " 检查许可");
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + " 成功通行");
        },"t1");
        t1.start();


        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "发放许可");
            LockSupport.unpark(t1);
        },"t2").start();
    }
}

 t1 检查许可
t2发放许可
t1 成功通行

 调用多次unpork()最多只能有1个凭证,调用两次park()却需要消费两个凭证,所以下面会阻塞

public class ParkDemo {
    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() + " 检查许可");
            LockSupport.park();
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + " 成功通行");
        }, "t1");
        t1.start();


        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "发放许可");
            LockSupport.unpark(t1);
            LockSupport.unpark(t1);
        }, "t2").start();
    }
}

当调用pork()方法时

  • 如果有凭证,则会直接消耗掉这个凭证然后正常退出;
  • 如果无凭证,就必须阻塞等待凭证可用;

而unpark()则相反, 它会增加一个凭证, 但凭证最多只能有1个, 累加无效。 

面试题 

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

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

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

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


网站公告

今日签到

点亮在社区的每一天
去签到