手把手教你构建源码级组件——Java互斥不可重入锁
Posted 杨 戬
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了手把手教你构建源码级组件——Java互斥不可重入锁相关的知识,希望对你有一定的参考价值。
文章目录
构造同步组件的步骤
之前的学习中我们学习了AQS的原理,其中有许多构建锁与同步器的相关概念我们需要了解到:
- 首先同步器是实现锁的关键,在锁的实现中聚合同步器,利用同步器实现锁的语义;
- 锁是面向使用者的,提供锁交互的实现;
- 同步器是面向锁的实现者,简化了锁的实现方式,屏蔽了同步状态管理、线程排队、等待/唤醒等底层操作。
从代码层面,同步器是基于模板模式实现的,可以通过AQS可重写的方法进行子类具体功能实现:
例如下面是AQS中tryAcquire模板方法的源码(如果子类没实现会怕抛出异常)
/**
* 模板方法:
* protected关键字
* 没有任何实现
* @param arg
* @return
*/
protected boolean tryAcquire(int arg)
throw new UnsupportedOperationException();
那么我们在构建同步组件的时候也就是需要实现以下几步:
1. 定义内部类Syn
随后将同步器组合在自定义同步组件的实现中,即定义内部类Syn继承AQS
public class XXX implements Lock
public class Sync extends AbstractQueuedSynchronizer
2. 继承同步器,重写指定方法
之后在Syn中重写AQS方法,根据同步器需求如下挑选实现不同方法
- tryAcquire(int arg):独占式获取同步状态;
- tryRelease(int arg):独占式释放同步状态;
- tryAcquireShared(int arg):共享式获取同步状态,返回大于0的值表示获取成功,否则失败
- tryReleaseShared(int arg):共享式释放锁
- isHeldExclusively():当前线程是否在独占模式下被线程占用,一般该方法表示是否被当前线程占用
例如不可重入同步器:
public class XXX implements Lock
public class Sync extends AbstractQueuedSynchronizer
@Override
protected boolean tryAcquire(int arg)
final Thread current = Thread.currentThread();
if (compareAndSetState(0, 1))
// 获取成功之后,当前线程是该锁的持有者,不需要再可重入数
setExclusiveOwnerThread(current);
return true;
return false;
@Override
protected boolean tryRelease(int arg)
if (getState() == 0)
throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);
return true;
@Override
protected boolean isHeldExclusively()
return getState() == 1;
// 返回Condition,每个Condition都包含了一个队列
Condition newCondition()
return new ConditionObject();
3. 调用同步器方法
最后调用同步器提供的模板方法,即同步组件类实现Lock方法之后,在lock/unlock方法中调用内部类Syn的方法acquire(int arg)等方法
public class XXX implements Lock
........
private final Sync sync = new Sync();
@Override
public void lock()
sync.acquire(1);
@Override
public void unlock()
sync.release(1);
........
具体请看下面的实验部分
互斥不可重入锁实现
在我之前写过的博文中(详解Java锁的升级与对比(1)——锁的分类与细节(结合部分源码))介绍可重入锁与不可重入锁的区别时,就写到JUC中没有不可重入锁的具体实现,但是可以类比,现在呢,我们可以做到实现了,具体看下面代码,模式完全符合依赖Lock与AQS构造同步组件模式。
代码实现
public class Mutex implements Lock
private final Sync sync = new Sync();
public class Sync extends AbstractQueuedSynchronizer
@Override
protected boolean tryAcquire(int arg)
final Thread current = Thread.currentThread();
if (compareAndSetState(0, 1))
// 获取成功之后,当前线程是该锁的持有者,不需要再可重入数
setExclusiveOwnerThread(current);
return true;
return false;
@Override
protected boolean tryRelease(int arg)
if (getState() == 0)
throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);
return true;
@Override
protected boolean isHeldExclusively()
return getState() == 1;
// 返回Condition,每个Condition都包含了一个队列
Condition newCondition()
return new ConditionObject();
@Override
public void lock()
sync.acquire(1);
@Override
public void unlock()
sync.release(1);
@Override
public void lockInterruptibly() throws InterruptedException
@Override
public boolean tryLock()
return false;
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException
return false;
@Override
public Condition newCondition()
return null;
其中核心代码就是重写的两个方法:
- tryAcquire(int arg)方法:主要是设置同独占式更新同步状态,CAS实现state+1
- tryRelease(int arg)方法:独占式释放同步状态,释放锁持有
测试Demo
package com.yyl.threadtest.utils;
import java.util.Date;
public class MutexDemo
public static void main(String[] args)
final Mutex lock = new Mutex();
class Worker extends Thread
@Override
public void run()
// 一直不停在获取锁
while (true)
lock.lock();
try
System.out.println(Thread.currentThread().getName() +" hold lock, "+new Date());
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
finally
lock.unlock();
System.out.println(Thread.currentThread().getName() +" release lock, "+new Date());
for (int i = 0; i < 10; i++)
Worker worker = new Worker();
// 以守护进程运行,VM退出不影响运行,这里只是为了一个打印效果,去掉注释一直打印
worker.setDaemon(true);
worker.start();
// 每隔一秒换行
for (int j = 0; j < 10; j++)
try
Thread.sleep(1000);
catch (InterruptedException e)
e.printStackTrace();
System.out.println();
运行结果
Thread-0 hold lock, Thu Feb 23 12:33:53 CST 2023
Thread-0 release lock, Thu Feb 23 12:33:54 CST 2023
Thread-1 hold lock, Thu Feb 23 12:33:54 CST 2023
Thread-1 release lock, Thu Feb 23 12:33:55 CST 2023
Thread-2 hold lock, Thu Feb 23 12:33:55 CST 2023
Thread-2 release lock, Thu Feb 23 12:33:56 CST 2023
Thread-3 hold lock, Thu Feb 23 12:33:56 CST 2023
Thread-3 release lock, Thu Feb 23 12:33:57 CST 2023
Thread-4 hold lock, Thu Feb 23 12:33:57 CST 2023
Thread-6 hold lock, Thu Feb 23 12:33:58 CST 2023
Thread-4 release lock, Thu Feb 23 12:33:58 CST 2023
Thread-6 release lock, Thu Feb 23 12:33:59 CST 2023
Thread-5 hold lock, Thu Feb 23 12:33:59 CST 2023
Thread-5 release lock, Thu Feb 23 12:34:00 CST 2023
Thread-7 hold lock, Thu Feb 23 12:34:00 CST 2023
Thread-7 release lock, Thu Feb 23 12:34:01 CST 2023
Thread-8 hold lock, Thu Feb 23 12:34:01 CST 2023
Thread-9 hold lock, Thu Feb 23 12:34:02 CST 2023
Thread-8 release lock, Thu Feb 23 12:34:02 CST 2023
Process finished with exit code 0
结果分析
互斥锁的核心就是同一个同步状态只能被一个线程持有,其它线程等待持有线程释放才能竞争获取。截图一开始的运行结果分析:
Thread-0 hold lock, Thu Feb 23 12:33:53 CST 2023
Thread-0 release lock, Thu Feb 23 12:33:54 CST 2023
Thread-1 hold lock, Thu Feb 23 12:33:54 CST 2023
Thread-1 release lock, Thu Feb 23 12:33:55 CST 2023
Thread-2 hold lock, Thu Feb 23 12:33:55 CST 2023
10个线程不断竞争锁,一开始Thread-0在08 12:33:53获取到锁,持有锁1秒后在释放12:33:54时释放,同时Thread-1立马获取到锁,1秒后于12:33:55释放锁,同时Thread-2立马获取到了锁…
根据输出结果来说,完全符合Mutex作为互斥锁这个功能:同一时刻只有一个线程持有锁(同步状态),其它线程等待释放后才能获取。
以上是关于手把手教你构建源码级组件——Java互斥不可重入锁的主要内容,如果未能解决你的问题,请参考以下文章
多线程 锁策略 ( 悲观/乐观锁 读写/互斥锁 重量/轻量级锁挂起等待/自旋锁 公平/非公平锁 可重入/不可重入锁)
Java 中15种锁的介绍:公平锁,可重入锁,独享锁,互斥锁,乐观锁,分段锁,自旋锁等等
Java 中15种锁的介绍:公平锁,可重入锁,独享锁,互斥锁,乐观锁,分段锁,自旋锁等等(转)