美文网首页
2022-05-06_JavaLockSupport示例互斥锁学

2022-05-06_JavaLockSupport示例互斥锁学

作者: kikop | 来源:发表于2022-05-06 11:23 被阅读0次

    20220506_JavaLockSupport示例互斥锁学习笔记.md

    1概述

    1.1LockSupport

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

    1.2核心函数

    public native void park(boolean isAbsolute, long time);
    public native void unpark(Thread thread);
    

    说明: 对两个函数的说明如下:

    park函数,阻塞线程,并且该线程在下列情况发生之前都会被阻塞: ① 调用unpark函数,释放该线程的许可。② 该线程被中断。③ 设置的时间到了。并且,当time为绝对时间时,isAbsolute为true,否则,isAbsolute为false。当time为0时,表示无限等待,直到unpark发生。
    unpark函数,释放线程的许可,即激活调用park后阻塞的线程。这个函数不是安全的,调用这个函数时要确保线程依旧存活。

    2代码示例

    模拟三个人过独木桥,每次只能过一个人。

    2.1MyFIFOMutexLock

    package com.kikop.mymutexdemo;
    
    import java.util.Queue;
    import java.util.concurrent.ConcurrentLinkedQueue;
    import java.util.concurrent.atomic.AtomicBoolean;
    import java.util.concurrent.locks.LockSupport;
    import java.util.concurrent.locks.ReentrantLock;
    
    
    /**
     * @author kikop
     * @version 1.0
     * @project myredissiondemo
     * @file MyFIFOMutexLock
     * @desc 官方给的LockSupport示例
     * 独占实现的是一个先进先出的线程等待队列
     * 不可重入
     * 使用park和unpark控制线程的阻塞和唤醒
     * @date 2022/5/6
     * @time 9:00
     * @by IDE IntelliJ IDEA
     */
    public class MyFIFOMutexLock {
    
        // 用来保证上一个占用资源的线程释放了资源,其它等待线程才可以获取资源
        private final AtomicBoolean locked = new AtomicBoolean(false);
    
        // 等待队列
        private final Queue<Thread> waiters = new ConcurrentLinkedQueue<Thread>();
    
        public void lock() {
            boolean wasInterrupted = false;
            Thread current = Thread.currentThread();
    
            // begin_无同一线程的判断(及不可重入)
            // end_无同一线程的判断(及不可重入)
    
            waiters.add(current);
    
    
            // Block while not first in queue or cannot acquire lock
            // while是为了防止虚假唤醒
            // 考虑在高铁站候车的场景
            // 1.闹钟、
            // 2.提前睡醒不满足条件则继续睡(虚假唤醒)
            while (waiters.peek() != current
                    || !locked.compareAndSet(false, true)) {
                LockSupport.park(this);  // this所属的当前线程,持有线程的某个对象
                if (Thread.interrupted()) // ignore interrupts while waiting
                    wasInterrupted = true;
            }
    
            waiters.remove();
            if (wasInterrupted) {
                // reassert interrupt status on exit
                current.interrupt();
            }
    
        }
    
        public void unlock() {
            locked.set(false); // 释放当前线程的资源
            LockSupport.unpark(waiters.peek());
        }
    }
    

    2.2MySingleBridgeTask

    package com.kikop.mymutexdemo;
    
    import java.util.concurrent.TimeUnit;
    
    
    /**
     * @author kikop
     * @version 1.0
     * @project myredissiondemo
     * @file MySingleBridgeTask
     * @desc 每次只能有一人过独木桥示例
     * @date 2022/5/6
     * @time 9:00
     * @by IDE IntelliJ IDEA
     */
    public class MySingleBridgeTask implements Runnable {
    
        private MyFIFOMutexLock myFIFOMutexLock;
    
        private String taskName;
        private int seq;
    
        /**
         * MySingleBridgeTask
         *
         * @param myFIFOMutexLock 全局锁
         */
        public MySingleBridgeTask(MyFIFOMutexLock myFIFOMutexLock, String taskName, int seq) {
            this.myFIFOMutexLock = myFIFOMutexLock;
            this.taskName = taskName;
            this.seq = seq;
        }
    
        @Override
        public void run() {
            try {
                System.out.println(String.format("--[%d]开始获取过桥通行证:%s...", seq, taskName));
                myFIFOMutexLock.lock();
                System.out.println(String.format("--[%d]获取过桥通行证成功:%s!", seq, taskName));
                System.out.println(String.format("--------[%d]开始过桥:%s...", seq, taskName));
                TimeUnit.SECONDS.sleep(5);
                System.out.println(String.format("--------[%d]完成过桥:%s!", seq, taskName));
            } catch (Exception ex) {
                ex.printStackTrace();
            } finally {
                System.out.println(String.format("--[%d]归还过桥通行证成功:%s!", seq, taskName));
                myFIFOMutexLock.unlock();
            }
    
        }
    }
    

    2.3testMyFiFoLock

    @Test
        public void testMyFiFoLock() throws InterruptedException {
    
            System.out.println("start main test...");
            CountDownLatch countDownLatch = new CountDownLatch(3);
    
            AtomicInteger atomicInteger = new AtomicInteger(0);
            // 1.创建一个锁
            MyFIFOMutexLock myFIFOMutex = new MyFIFOMutexLock();
    
            // 2.业务任务
            Thread student_zhangsan = new Thread(new MySingleBridgeTask(myFIFOMutex, "zhangsan", atomicInteger.incrementAndGet()));
            student_zhangsan.start();
    
            Thread student_xiaoming = new Thread(new MySingleBridgeTask(myFIFOMutex, "xiaoming", atomicInteger.incrementAndGet()));
            student_xiaoming.start();
    
            Thread student_kikop = new Thread(new MySingleBridgeTask(myFIFOMutex, "kikop", atomicInteger.incrementAndGet()));
            student_kikop.start();
    
            // 3.确保任务执行完成
            countDownLatch.await(20, TimeUnit.SECONDS);
            System.out.println("end main test!");
    
        }
    

    总结

    3.1LockSupport vs Synchronized

    3.1.1无法唤醒指定的线程

    LockSupport的意思就是有两个方法park()和unpark(thread)控制线程的阻塞和唤醒。park()是说当前线程执行该方法后进入阻塞状态,需要再调用unpark(thread)解除阻塞限制。如果unpark(thread)先于park()执行,则该次park()不起作用不会使得线程阻塞。
    我们可以使用它来阻塞和唤醒线程,功能和wait,notify有些相似,但是LockSupport比起wait,notify功能更强大。

    • wait和notify都是Object中的方法,在调用这两个方法前必须先获得锁对象,这限制了其使用场合:只能在同步代码块中。
    • 当对象的等待队列中有多个线程时,notify只能随机选择一个线程唤醒,无法唤醒指定的线程。

    3.1.2顺序

    如果先调用notify,再调用wait,将起不了作用。

    LS保证下一次调用 park 不会受阻塞。如果给定线程尚未启动,则无法保证此操作有任何效果。相当于提前给Boss在路口放好许可证,当boss被拦截时,立马唤醒放行。原因是JVM在缓存总记录了某个线程的许可,Why

    3.2示例中AtomicBoolean作用

    AtomicBoolean用来保证上一个占用资源的线程释放了资源,其它等待线程才可以获取资源。
    【等待队列】中当前线程已经排在了第一个,但是上一个执行线程还没有释放资源,当前线程依旧不可以执行,需要阻塞unPark。

    只有当满足如下条件,线程业务处理才能继续:

    【等待队列】中当前线程排在第一个,

    且资源(AtomicBoolean就是资源是否释放的标识)已经释放

    3.2虚假唤醒(重新进行条件判断,增加二次while循环判断)

    “多线程环境下,有多个线程执行了wait()方法,需要其他线程执行notify()或者notifyAll()方法去唤醒它们,假如多个线程都被唤醒了,但是只有其中一部分是有用的唤醒操作,其余的唤醒都是无用功;对于不应该被唤醒的线程而言,便是虚假唤醒。 比如:仓库有货了才能出库,突然仓库入库了一个货品;这时所有的线程(货车)都被唤醒,来执行出库操作;实际上只有一个线程(货车)能执行出库操作,其他线程都是虚假唤醒。”

    参考

    1JUC之LockSupport-多线程与高并发

    https://zhuanlan.zhihu.com/p/268373312

    2JUC Lock:LockSupport详解

    https://blog.csdn.net/qq_38327769/article/details/124212033

    3JUC整体结构

    https://blog.csdn.net/qq_38327769/article/details/124169396?spm=1001.2014.3001.5501

    相关文章

      网友评论

          本文标题:2022-05-06_JavaLockSupport示例互斥锁学

          本文链接:https://www.haomeiwen.com/subject/fjgayrtx.html