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-多线程与高并发
2JUC Lock:LockSupport详解
3JUC整体结构
https://blog.csdn.net/qq_38327769/article/details/124169396?spm=1001.2014.3001.5501
网友评论