JDK中的锁大致可以分为两类:Synchronized和CAS。
CAS的使用中,有直接使用的,比如AtomicInteger;有间接使用的比如ReentrantLock。关于AtomicInteger的分析可参考笔者的上一篇博客:
CAS间接实现的典型代表是ReentrantLock和ReentrantReadWriteLock。
本文先不具体介绍ReentrantLock的实现方式,而且先介绍一下其基础类AbstractQueuedSynchronizer类,该类作为CAS的抽象类实现类,是并发包(java.util.concurrent)下重要的基础类。
1.AbstractQueuedSynchronizer(AQS)使用范例
AQS是一个用来构建锁和同步器的框架。
以下范例来自于网络(有删减):https://www.cnblogs.com/chengxiao/p/7141160.html
1)创建Mutex类用于实现AQS
public class Mutex implements java.io.Serializable {
//同步对象完成一系列复杂的操作,我们仅需指向它即可
private final Sync sync = new Sync();
//加锁操作,代理到acquire(模板方法)上就行,acquire会调用我们重写的tryAcquire方法
public void lock() {
sync.acquire(1);
}
public boolean tryLock() {
return sync.tryAcquire(1);
}
//释放锁,代理到release(模板方法)上就行,release会调用我们重写的tryRelease方法。
public void unlock() {
sync.release(1);
}
public boolean isLocked() {
return sync.isHeldExclusively();
}
/** 注意这个静态内部类 */
//静态内部类,继承AQS
private static class Sync extends AbstractQueuedSynchronizer {
//是否处于占用状态
protected boolean isHeldExclusively() {
return getState() == 1;
}
//当状态为0的时候获取锁,CAS操作成功,则state状态为1,
public boolean tryAcquire(int acquires) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
//释放锁,将同步状态置为0
protected boolean tryRelease(int releases) {
if (getState() == 0) throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);
return true;
}
}
}
总结:本例中使用静态内部值Sync实现AQS,主要实现其tryAcquire()和tryRelease()方法
2)测试类
public class TestMutex {
private static CyclicBarrier barrier = new CyclicBarrier(31);
private static int a = 0;
private static Mutex mutex = new Mutex();
public static void main(String []args) throws Exception {
//说明:我们启用30个线程,每个线程对i自加10000次,同步正常的话,最终结果应为300000;
//加锁后
barrier.reset();//重置CyclicBarrier
a=0;
for(int i=0;i0,说明是CANCELLED状态,则说明前节点已经无效,则从后向前遍历,找到一个非CANCELLED状态
// 的节点,并将当前节点设置为其后置节点
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 其他情况(CONDITION、PROPAGATE)这两种状态的前节点,将前节点设置为SIGNAL状态
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
// 检查线程是否被中断过,如果被设置过则返回true
private final boolean parkAndCheckInterrupt() {
// 当前线程进入wait
LockSupport.park(this);
return Thread.interrupted();
}
总结:加锁过程可总结为以下三步
* 首先通过tryAcquire()方法来获取同步状态,成功则返回,失败进行下一步骤操作
* 创建Node节点,加入同步队列中
* 加入队列的Node节点,如果其前节点是head,则再次调用tryAcquire()方法来获取同步状态。否则当其前节点的状态为SIGNAL时,线程便进入阻塞状态,直至被中断或唤醒
用图再来说明一下整个过程,图片来自(http://www.cnblogs.com/waterystone/p/4920797.html ):
做一个比喻来说:
我们去医院排队挂号,等医生看病,这个资源就是医生本身,医生每一次只能给一位挂号的病人看病,目前在看病的也就是head节点。
1)又来了一位病人
他去尝试获取state,也就是让医生帮看病,发现资源被人占用(因为head在医生那正看着呢),所以他就去排队,排到队尾,也就是tail节点,然后就去休息(执行了LockSupport.park(this)方法)
2)第一个病人看好了病之后
他去释放锁,也就是从医生那出来,(喊了一嗓子,大家可以去医生那看病啦,我看好了,执行LockSupport.unpark方法),然后休息的线程都醒来了,去竞争资源,但是只有等待线程2竞争资源成功,那么他接着进去看病,他就被设置为head
3.Mutex.unlock()释放锁
public void unlock() {
sync.release(1);
}
// AbstractQueuedSynchronizer.release()
public final boolean release(int arg) {
// tryRelease方法调用的是Mutex.tryRelease()
if (tryRelease(arg)) {
// 获取头结点,如果头结点状态不为0,目前是SIGNAL=-1
Node h = head;
if (h != null && h.waitStatus != 0)
// 则执行unparkSuccessor方法
unparkSuccessor(h);
return true;
}
return false;
}
1) AbstractQueuedSynchronizer.unparkSuccessor()
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
// 如果head的状态为SIGNAL,则设置为0
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
// 非正常情况下,head的next结点状态为CANCELLED,则从tail结点向前寻找,
// 找到一个正常结点后将其设置为head的next结点
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?