• 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方法,也会唤醒该线程

  • 相关阅读:
    CentOS 6.3下部署LVS(NAT)+keepalived实现高性能高可用负载均衡
    三大WEB服务器对比分析(apache ,lighttpd,nginx)
    linux sudo 命令
    linux 添加用户、权限
    LeetCode——Find Largest Value in Each Tree Row
    LeetCode——Single Element in a Sorted Array
    LeetCode——Find All Duplicates in an Array
    LeetCode—— Partition Equal Subset Sum
    LeetCode——Unique Binary Search Trees II
    LeetCode——Is Subsequence
  • 原文地址:https://www.cnblogs.com/zoran0104/p/15106566.html
Copyright © 2020-2023  润新知