ReentrantLock是一种基于AQS框架的实现,也是JDK1.5后的一种线程并发访问的同步手段,它类似于synchronized是一种互斥锁,可以保证线程安全。它比synchronized更多的特性,比如它支持加锁的公平性、支持手动加锁与解锁。
import java.util.concurrent.locks.ReentrantLock;
/**
* @program: 多线程测试
* @description:
* @author: cc百川 https://blog.csdn.net/ljcc122/
* @create: 2021-04-10 13:52
**/
public class Test1_UserLock {
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
ReentrantLock rlock = new ReentrantLock();//*1* 默认申请非公平锁[^1] NonfairSync
for(int i=0;i<10;i++) {
new Thread(()->{
doRun(rlock);
}, i+"").start();
}
Thread.sleep(1000);
System.out.println(count);
}
/**
* 线程run方法内部逻辑
* @param rlock
*/
public static void doRun(ReentrantLock rlock){
rlock.lock();//*2*锁竞争标识
//获得锁后进入下面的逻辑,没有获取锁的进入自旋等待当前锁释放*3*
try{
for(int i=0;i<100;i++)
count++;
} finally {
rlock.unlock();//用完后释放锁,这也就是与synchronized的区别之一需要显示的加释锁
}
}
}
公平锁简单理解就是不区分身份,每一个任务都需要排队。比如:高考进场,不区分你是班长还是课代表,都需要排队进场。
非公平锁简单理解就是,任务由高低可以插队。比如:银行VIP机制,VIP不管后面有没有排队都插队。
//加锁核心
public final void acquire(int arg) {
//尝试获取锁,获取失败则自旋进入同步队列,并阻塞自己 LockSupport.park(this);
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//AQS核心同步等待CLH队列(双向链表) 中已Node的方式存储
selfInterrupt();
}
//入队
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) //设置当前节点的前节点的waitStatus=-1 用于标记是否能唤醒当前节点
&& parkAndCheckInterrupt())//阻塞
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
//释放核心
//释放锁
public void unlock() {
sync.release(1);
}
//尝试释放资源
public final boolean release(int arg) {
if (tryRelease(arg)) {//尝试释放资源就是设置state=0,并通知CLH队列,可以进行锁竞争了
Node h = head;
if (h != null && h.waitStatus != 0)//这里或判断队列的第一个节点waitStatus 状态是否不等于0,不等于0说明可以唤醒队列中的头节点的下一个节点
unparkSuccessor(h);//拿到头节点下一个节点的线程,重置下一个节点的数据含设置下一个节点waitStatus=0,头节点后移自旋处会修改为-1
return true;
}
return false;
}
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
设置加锁步骤:
第一步:直接尝试cas设置锁抢占。如果抢占成功就执行当前任务。
*/
final void lock() {
if (compareAndSetState(0, 1))//直接参数锁的抢占
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);//尝试获取资源,成功则返回true,失败则返回false
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();//AQS状态标记 等于0表示当前没有执行的任务,可以继续执行任务
if (c == 0) {//等于0标识可以进行锁的争夺
if (compareAndSetState(0, acquires)) {//通过CAS争夺锁,争夺成功执行自己的线程任务
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {//可重入标志,如果当前线程是正在执行任务的线程,再次加锁不需要争夺锁资源,给AQS的状态为+acquires【这里为1】即可,这里也不用考虑线程安全问题,能执行任务的只有一个线程。
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);//尝试获取资源,成功则返回true,失败则返回false
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&//这里多了一步判断因为公平锁需要判断只有队列中的第一个才能最先去获取锁资源
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {//道理同非公平锁相同代码段
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
//判断当前队列是否为空或者当前线程是队列第一个
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
指定线程的阻塞与唤醒。
内部使用Unsafe的park阻塞、唤醒线程。
public class Test2_LockSuport {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
LockSupport.park();//阻塞当前线程
System.out.println(Thread.currentThread().getName());
}, "cc百川lockA");
Thread t2 = new Thread(()->{
LockSupport.park();//阻塞当前线程
System.out.println(Thread.currentThread().getName());
}, "cc百川lockB");
t1.start();
t2.start();
Thread.sleep(1000);
System.out.println("主线程唤醒");
LockSupport.unpark(t1);//主线程唤醒t1线程
}
}
结果
共同点
LockSupport中的park方法和Object中的wait方法都可以使线程进入WAIT或者TIMED_WAIT状态
LockSupport中的unpark方法和Object中的notify可以使线程脱离WAIT、TIMED_WAIT状态
不同点
Object中的wait方法在调用时当前线程必须要对该Object进行加锁
Object中wait和notify方法必须要按顺序调用。
Object中notify不能指定唤醒哪一个线程,之鞥呢通过notifyAll唤醒所有线程。而LockSupport.ubpark(Thread)可以唤醒指定线程。
notify()随机唤醒一个线程,notifyAll()会唤醒所有的线程
唤醒的线程由等待池进入线程锁池中,参与 锁的竞争,竞争成功则执行,如不成功则停 留在锁池中等待锁被释放后,再参与竞争
注意:wait与Thread.sleep区别参考:多线程系列:Java创建线程的方式
AQS(AbstractQueuedSynchronizer)主要利用 CAS (Compare AndSwap)和volatile和native方法来保证原子操作, 从而避免synchronized的高开销,执行效率大为提升,基于FIFO的CLH队列保证了同步阻塞。Unsafe请参考:多线程系列:阻塞队列BlockingQueue分析
重要volatile变量 state记录当前是否存在线程在执行任务,(state>0)
1.阻塞等待队列
2.独占、共享
3.公平与非公平
4.允许中断
5.可重入
start用于启动线程,可以进入线程的生命周期,start只能调用一次。
run方法用于指向线程的运行时代码,run可以重复调用(类似普通方法)。
让当前正在运行的线程回到可运行状态,允许相同优先级的其他线程,参与锁竞争运行的机会。
synchronized是由一对monitorenter/monitorexit指令实现的, monitor对象是同步的基本实现单
主要是说的JDK1.6之后
无锁->偏向锁->轻量级锁->重量级锁【注意锁的膨胀不可逆】即升级后不能退回到上一个状态
锁类型 | 原因 |
---|---|
偏向锁 | 无实际竞争,且自由一个线程获得锁的全部使用 |
轻量级锁 | 无实际竞争,多个线程交替使用锁,运行短时间锁的竞争 |
重量级锁 | 有实际竞争,且锁竞争时间较长 |
需要开启JVM逃逸分析【其他作用标量替换,把对象分配到栈中,减少GC次数】
类型 | 说明 | 图解 |
---|---|---|
粗化 | 指把同一个对象的多个锁粗化为一个锁,在同一个方法中多次对同一个对象加锁,理论上是无意义的,其他线程获取了当前锁 | |
消除 | 指把无用锁消除,这里在方法类加锁且对象不存在竞争即可以不加 |
持续完善中,望各位看官指导
文章目录 前言 本周最重要的五件事情 本周搞砸的四件事情 本周的四个启发 前言 ...
2 月 18 日消息 据外媒 Windowslatest 报道,在预览版本中发现的参考资料表明,...
第一课趣味二进制——修改植物大战僵尸数据 任务介绍 学习目标 知识需求 需求工...
IT之家2月18日消息外媒 Windows Latest 报道,微软正在与谷歌合作进行一项新的改...
首先插件配备好了,写一个HTML测试一下 首先创建一个文件夹,创建一个HTML 文件...
JSP spring boot / cloud 使用filter防止XSS 一.前言 XSS(跨站脚本攻击) 跨站脚...
1.ajax跨域传递值是所需要的回传的类型为jsonp $.ajax({url: "http://.......",t...
昨天刚学了html的一些内容,就迫不及待的想做个京东上面的搜索条,结果做是做出...
在项目开始之前我们可以先去了解一下IConfiguration接口,.Net Core Web应用程序...
一.前言 .NET Core 是一个通用开发平台,由 Microsoft 和 GitHub 上的 .NET 社区...