zoukankan      html  css  js  c++  java
  • JUC(6)LockSupport

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

    线程等待唤醒机制

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

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

    wait()和notify()存在的不足

    public class LockSupportDemo {
        public static void main(String[] args) {
            Object objectLock = new Object(); 
            new Thread(() -> {
                synchronized (objectLock) {
                    try {objectLock.wait();} catch (InterruptedException e) {e.printStackTrace();}
                }
                System.out.println(Thread.currentThread().getName() + "	" + "被唤醒了");
            }, "t1").start();
    
            //暂停几秒钟线程
            try {TimeUnit.SECONDS.sleep(3L);} catch (InterruptedException e) {e.printStackTrace();}
    
            new Thread(() -> {
                synchronized (objectLock) {
                    objectLock.notify();
                }
            }, "t2").start();
        }
    }
    

    这段代码运行完全没有任何问题,一个很正常的等待-唤醒流程。

    但是,wait()和notify()方法能否脱离同步代码块呢?当我们作出这个尝试运行后发现,代码报了java.lang.IllegalMonitorStateException异常

    那么我们能否更换wait()和notify()的执行顺序呢?也就是先执行notify方法在执行wait方法,我们发现,代码虽然表面上没有报错,但是执行wait的那个线程一直在等待下一个唤醒他的线程。

    故而我们得出结论· wait和notify方法必须要在同步块或者方法里面,且成对出现使用,先wait后notify才OK。

    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() + "	" + "start");
                    condition.await();
                    System.out.println(Thread.currentThread().getName() + "	" + "被唤醒");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }, "t1").start();
          
            //暂停几秒钟线程
            try {TimeUnit.SECONDS.sleep(3L);} catch (InterruptedException e) {e.printStackTrace();}
    
            new Thread(() -> {
                lock.lock();
                try {
                    condition.signal();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
                System.out.println(Thread.currentThread().getName() + "	" + "通知了");
            }, "t2").start();
        }
    }
    

    与之前的尝试一样,await和signal方法能否脱离lock()方法使用呢?显然不行,和同步代码块一样,也报了java.lang.IllegalMonitorStateException异常,同样的先调用signal方法再调用await方法也会导致调用await的方法一直阻塞。

    所以对于await和signal方法,我们可以得出结论

    • Condtion中的线程等待和唤醒方法之前,需要先获取锁
    • 一定要先await后signal,不要反了

    以上两组方法的测试我们可以得出结论

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

    LockSupport

    · 通过park()和unpark(thread)方法来实现阻塞和唤醒线程的操作

    This class associates, with each thread that uses it, a permit (in the sense of the Semaphore class). A call to park will return immediately if the permit is available, consuming it in the process; otherwise it may block. A call to unpark makes the permit available, if it was not already available. (Unlike with Semaphores though, permits do not accumulate. There is at most one.)

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

    pack()-阻塞当前线程/阻塞传入的具体线程

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

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

    unpack()-唤醒处于阻塞状态的指定线程

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

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

    public class LockSupportDemo {
        public static void main(String[] args) {
            //正常使用+不需要锁块
            Thread t1 = new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + " " + "1111111111111");
                LockSupport.park();
                System.out.println(Thread.currentThread().getName() + " " + "2222222222222------end被唤醒");
            }, "t1");
            t1.start();
            
            //暂停几秒钟线程
            try {TimeUnit.SECONDS.sleep(3L);} catch (InterruptedException e) {e.printStackTrace();}
          
            LockSupport.unpark(t1);
            System.out.println(Thread.currentThread().getName() + "   -----LockSupport.unparrk() invoked over");
    
        }
    }
    

    上述代码明显它不需要必须在锁块中调用,那么如果我们先执行unpack方法再执行pack方法,pack所在的线程会被阻塞吗?不会!因为unpack方法执行后,permit被置为1,当pack方法需要时,拿到permit发现是1,消费即可,不会阻塞

    他解决了之前线程等待唤醒机制存在的两大问题

    1. LockSupport不用持有锁块,不用加锁,程序性能好

    2. 先后执行顺序不影响是否导致阻塞

    值得注意的是,如果我们先执行两次unpack方法,再执行两次pack方法,我们发现,线程被阻塞住了,因为unpack两次,并不会+2,许可证仍是1,第一个pack方法使用后置为0,第二个pack没有许可证也就阻塞住了。

    总结

    • LockSupport是一个线程阻塞工具类,所有的方法都是静态方法,可以让线程在任意位置阻塞,阻塞之后也有对应的唤醒方法。本质调用的Unsafe中的native方法。

    • LockSupport和每个使用它的线程都有一个许可(permit)关联,permit相当于0和1的开关,默认是0.

    • 每个线程都有一个相关的permit,permit最多有一个,重复调用unpack不会积累凭证。

    • 当调用pack方法时

      • 如果有凭证,消耗凭证正常结束阻塞
      • 如果没有凭证,就阻塞直到凭证可用(为1)
    • 当调用unpack方法时,它会增加一个凭证,但凭证最多只能有一个,累加无效

    • 调用被pack方法阻塞的线程的interrupt方法,也会唤醒该线程

  • 相关阅读:
    【codecombat】 试玩全攻略 第九关 循环又循环
    【codecombat】 试玩全攻略 第十三关 已知敌人
    【codecombat】 试玩全攻略 第十一关 再次迷宫经历
    【codecombat】 试玩全攻略 第六关 cell commentary
    【codecombat】 试玩全攻略 第八关 火舞
    【codecombat】 试玩全攻略 第十二关 恐惧之门
    【codecombat】 试玩全攻略 第十四关 已知敌人
    苹果apns推送总结
    Xcode 提升速度小技巧
    UITextField 限制输入字数
  • 原文地址:https://www.cnblogs.com/zoran0104/p/15106566.html
Copyright © 2011-2022 走看看