LockSupport

Mr.LR2022年6月6日
大约 4 分钟

LockSupport

线程等待和唤醒方法

了解LockSupport前,先回顾2种让线程等待和唤醒的方法

方式一

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

public  void test1() {
    Object objectLock = new Object(); //同一把锁,类似资源类
    new Thread(() -> {
        synchronized (objectLock) {
            System.out.println(Thread.currentThread().getName()+" -------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()+"  ------通知");
        }
    },"t2").start();
}

执行结果
t1 -------come in
t2  ------通知
t1	被唤醒了    

异常情况一

如果去掉synchronized,即不在synchronized关键字方法中使用wait() 和 notify() 方法将抛出 java.lang.IllegalMonitorStateException 异常

t1 -------come in
Exception in thread "t1" java.lang.IllegalMonitorStateException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at com.company.jucmax.LockSupport.LockSupportDemo.lambda$test1notsyn$2(LockSupportDemo.java:44)
	at java.lang.Thread.run(Thread.java:750)
Exception in thread "t2" java.lang.IllegalMonitorStateException
	at java.lang.Object.notify(Native Method)
	at com.company.jucmax.LockSupport.LockSupportDemo.lambda$test1notsyn$3(LockSupportDemo.java:57)
	at java.lang.Thread.run(Thread.java:750)

异常情况二

先 notify() 后 wait() t1线程无法被唤醒

image-20220509112228594

总结

wait和notify方法必须要在同步块或者方法里面且成对出现使用

先wait后notify才可以

方式二

使用JUC包中Condition的await()方法让线程等待,使用signal()方法唤醒线程

public void lock() {
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    new Thread(() -> {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "----come in");
            try {
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()  + "------被唤醒");
        } finally {
            lock.unlock();
        }
    }, "t1").start();


    new Thread(() -> {
        lock.lock();
        try {
            condition.signal();
            System.out.println(Thread.currentThread().getName() + "------通知");
        } finally {
            lock.unlock();
        }
    }, "t2").start();
}
运行结果
t1----come in
t2------通知
t1------被唤醒    

异常情况一

不在 lock() 和 unlock() 方法内使用 await() 和 signal() 方法,方法将抛出 java.lang.IllegalMonitorStateException 异常

t1----come in
Exception in thread "t1" Exception in thread "t2" java.lang.IllegalMonitorStateException
	at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.fullyRelease(AbstractQueuedSynchronizer.java:1723)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2036)
	at com.company.jucmax.LockSupport.LockSupportDemo.lambda$locknotlock$8(LockSupportDemo.java:139)
	at java.lang.Thread.run(Thread.java:750)
java.lang.IllegalMonitorStateException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.signal(AbstractQueuedSynchronizer.java:1939)
	at com.company.jucmax.LockSupport.LockSupportDemo.lambda$locknotlock$9(LockSupportDemo.java:153)
	at java.lang.Thread.run(Thread.java:750)

异常情况二

先 signal() 后 await() t1线程无法被唤醒

image-20220509115212503

总结

Condtion中的线程等待和唤醒方法之前,需要先获取锁

先await后signal

总结

Object和Condition使用的限制条件:

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

因此出现LockSupport 来实现线程的阻塞和唤醒

LockSupport简介

  • 用于创建锁和其他同步类的基本线程阻塞原语。
  • 简而言之,当调用LockSupport.park时,表示当前线程将会等待,直至获得许可,当调用LockSupport.unpark时,必须把等待获得许可的线程作为参数进行传递,好让此线程继续运行。

image-20220509104554361

  • LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能, 每个线程都有一个许可(permit),permit只有两个值1和零,默认是零。
  • 可以把许可看成是一种(0,1)信号量(Semaphore),但与 Semaphore 不同的是,许可的累加上限是1。

主要实现方法

阻塞

park() /park(Object blocker)

阻塞当前线程/阻塞传入的具体线程

public static void park() {
    UNSAFE.park(false, 0L);
}

permit默认是零,所以一开始调用park()方法,当前线程就会阻塞,直到别的线程将当前线程的permit设置为1时,park方法会被唤醒, 然后会将permit再次设置为零并返回。

唤醒

unpark(Thread thread)

唤醒处于阻塞状态的指定线程

public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
}

调用unpark(thread)方法后,就会将thread线程的许可permit设置成1(注意多次调用unpark方法,不会累加,permit值还是1)会自动唤醒thread线程,即之前阻塞中的LockSupport.park()方法会立即返回。

代码

正常+无锁块要求

public  void test1notsyn() {
    Thread t1 =  new Thread(() -> {
        System.out.println(Thread.currentThread().getName()+" -------come in");
        try {
            LockSupport.park();
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"\t"+"被唤醒了");
    },"t1");
    t1.start();
    //暂停几秒钟线程
    try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { e.printStackTrace(); }

    new Thread(() -> {
        LockSupport.unpark(t1);
        System.out.println(Thread.currentThread().getName()+"  ------通知");
    },"t2").start();
}
t1 -------come in
t2  ------通知
t1	被唤醒了
  • 即使先唤醒,后等待也可以执行
public static void main(String[] args) {
    Thread t1 =  new Thread(() -> {
        try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println(Thread.currentThread().getName()+" -------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()+"  ------通知");
    },"t2").start();
}
t2  ------通知
t1 -------come in
t1	被唤醒了

综上:LockSupport不用担心阻塞和唤醒操作的顺序,但要注意连续多次唤醒的效果和一次唤醒是一样的。

参考

上次编辑于: 2022/6/6 22:21:59
贡献者: liurui_60837