2022-05-06_JavaLockSupport示例互斥锁学习笔记

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 waiters = new ConcurrentLinkedQueue();

    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示例互斥锁学习笔记)