这是我参与8月更文挑战的第9天,活动详情查看:8月更文挑战
往期推荐
一、ReentrantLock
ReentrantLock
也称作可重入锁,是在Java并发编程中常见的一种并发编程工具,通常用于保障代码段线程安全,ReentrantLock
的实现主要依靠AbstractQueuedSynchronizer
类。
class ReentrantLockDemo{
/**
* 锁对象
*/
private static Lock lock;
public static void main(String[] args)throws InterruptedException{
System.out.println("ReentrantLock 公平锁使用方式演示:");
//公平锁模式
lock =new ReentrantLock(true);
for (int i=0; i<5;i++){
new Thread(new ThreadDemo(i)).start();
}
Thread.sleep(1000);
System.out.println("ReentrantLock 非公平锁使用方式演示:");
//非公平锁模式
lock =new ReentrantLock(false);
for (int i=0; i<5;i++){
new Thread(new ThreadDemo(i)).start();
}
}
/**
* 实现Runnable接口
*/
static class ThreadDemo implements Runnable{
Integer id;
public ThreadDemo(Integer id){
this.id=id;
}
@Override
public void run(){
try{
TimeUnit.MILLISECONDS.sleep(10);
}catch (InterruptedException e){
e.printStackTrace();
}
for (int i=0;i<2;i++){
lock.lock();
System.out.println("获得锁的线程:"+id);
lock.unlock();
}
}
}
}
ReentrantLock 公平锁使用方式演示:
获得锁的线程:4
获得锁的线程:0
获得锁的线程:3
获得锁的线程:2
获得锁的线程:1
获得锁的线程:4
获得锁的线程:0
获得锁的线程:3
获得锁的线程:2
获得锁的线程:1
ReentrantLock 非公平锁使用方式演示:
获得锁的线程:3
获得锁的线程:3
获得锁的线程:1
获得锁的线程:1
获得锁的线程:2
获得锁的线程:2
获得锁的线程:4
获得锁的线程:4
获得锁的线程:0
获得锁的线程:0
二、ReentrantLock的三大内部类
2.1 ReentrantLock类图
ReentrantLock
类内部共存在Sync
、NonfairSync
、FairSync
三个内部类。
NonfairSync
与FairSync
类继承自Sync类Sync
类继承自AbstractQueuedSynchronizer
抽象类
2.2 Sync类
/**
* 可重入锁基础的同步控制器
*/
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
/**
* 加锁方法,由Sync的子类FairSync、NonfairSync实现
*/
abstract void lock();
/**
* 非公平锁
*/
final boolean nonfairTryAcquire(int acquires) {
//当前线程
final Thread current = Thread.currentThread();
//获取锁的状态,即AQS中的state状态
int c = getState();
// c等于0 表示没有线程持有该锁
if (c == 0) {
//CAS修改state状态,如果CAS成功,即加锁成功
if (compareAndSetState(0, acquires)) {
//设置当前线程以独占的方式持有锁
setExclusiveOwnerThread(current);
//返回加锁成功
return true;
}
}
//如果c不等于0,表示此时锁被某个线程占有
//如果当前的线程就是锁的持有者
else if (current == getExclusiveOwnerThread()) {
//发生重进入,增加重进入的次数
int nextc = c + acquires;
//如果 nextc 小于0,即发生了int类型的溢出
if (nextc < 0)
// overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
// 试图在共享模式下获取对象状态,此方法应该查询是否允许它在共享模式下获取对象状态,如果允许,则获取它
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread()) // 当前线程不为独占线程
throw new IllegalMonitorStateException(); // 抛出异常
// 释放标识
boolean free = false;
if (c == 0) {
free = true;
// 已经释放,清空独占
setExclusiveOwnerThread(null);
}
// 设置标识
setState(c);
return free;
}
// 判断资源是否被当前线程占有
protected final boolean isHeldExclusively() {
// While we must in general read state before owner,
// we don't need to do so to check if current thread is owner
return getExclusiveOwnerThread() == Thread.currentThread();
}
// 新生一个条件
final ConditionObject newCondition() {
return new ConditionObject();
}
// Methods relayed from outer class
// 返回资源的占用线程
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
// 返回状态
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
// 资源是否被占用
final boolean isLocked() {
return getState() != 0;
}
/**
* Reconstitutes the instance from a stream (that is, deserializes it).
*/
// 自定义反序列化逻辑
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
Sync类方法和作用:
方法 | 作用 |
---|---|
lock | 加锁方法,由Sync的子类FairSync、NonfairSync实现 |
nonfairTryAcquire | 非公平加锁 |
tryRelease | 试图在共享模式下获取对象状态,此方法应该查询是否允许它在共享模式下获取对象状态 |
isHeldExclusively | 检验当前的线程是否与持有锁的线程相等 |
newCondition | 返回一个ConditionObject对象 |
getOwner | 返回锁的持有者 |
getHoldCount | 返回重入锁重进入的次数 |
isLocked | 判断是否已经被加锁 |
readObject | 通过流反序列化对象 |
2.3 FairSync类
FairSync类也继承了Sync类,表示采用公平策略获取锁,其实现了Sync类中的抽象lock方法
/**
* FairSync继承Sync,实现公平锁
*/
static final class FairSync extends ReentrantLock.Sync {
private static final long serialVersionUID = -3000897897090466540L;
/**
* 加锁
*/
final void lock() {
acquire(1);
}
/**
* 调用子类的tryAcquire()实现
*/
protected final boolean tryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 获取状态
int c = getState();
if (c == 0) { // 状态为0
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) { // 不存在已经等待更久的线程并且比较并且设置状态成功
// 设置当前线程独占
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) { // 状态不为0,即资源已经被线程占据
// 下一个状态
int nextc = c + acquires;
if (nextc < 0) // 超过了int的表示范围
throw new Error("Maximum lock count exceeded");
// 设置状态
setState(nextc);
return true;
}
return false;
}
}
2.4 NonfairSync类
NonfairSync类继承了Sync类,表示采用非公平策略获取锁,其实现了Sync类中抽象的lock方法
// 非公平锁
static final class NonfairSync extends Sync {
// 版本号
private static final long serialVersionUID = 7316153563782823691L;
// 获得锁
final void lock() {
if (compareAndSetState(0, 1)) // 比较并设置状态成功,状态0表示锁没有被占用
// 把当前线程设置独占了锁
setExclusiveOwnerThread(Thread.currentThread());
else // 锁已经被占用,或者set失败
// 以独占模式获取对象,忽略中断
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
Nonfair
和FairSync
的区别
- FairSync类的lock的方法中可以看出只要资源被其他线程占用,该线程就会添加到sync queue中的尾部,而不会先尝试获取资源。
- Nonfair类的lock的方法每一次都会尝试去获取资源,如果此时该资源恰好被释放,则会被当前线程获取,这就造成了不公平的现象,当获取不成功,再加入队列尾部。
三、公平锁和非公平锁
3.1 公平锁
ReentrantLock
以公平锁方式进行加锁时,调用的FairSync
类的lock()方法调用AbstractQueuedSynchronizer
类的acquire()方法实现对资源的加锁。
/**
* FairSync 方式加锁
*/
final void lock() {
acquire(1);
}
/**
* AQS的acquire()方法
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
接着AbstractQueuedSynchronizer
类的tryAcquire()方法是需要其子类实现的,若FairSync
的tryAcquire()方法返回true,当前线程加锁成功,则ReentrantLock
的公平锁模式加锁成功;否则ReentrantLock
公平锁模式加锁失败。
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;
}
}
ReentrantLock公平锁执行流程图:
ReentrantLock
锁的释放是通过unlock()方法实现的,调用AbstractQueuedSynchronizer
类的release()方法实现解锁,ReentrantLock
的内部类Sync
实现了tryRelease()方法。
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
3.2 非公平锁
ReentrantLock
的非公平加锁是通过调用NonfairSync
类的lock()方法实现的。lock()方法将会进入AbstractQueuedSynchronizer
中的acquire()方法执行。
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
NonfairSync
中的tryAcquire()方法调用其父类Sync
的nonfairTryAcquire()方法。
如果nonfairTryAcquire()方法返回false,即非公平锁加锁失败,程序就会执行到AbstractQueuedSynchronizer
类的addWaiter()方法,接下来将调用以下代码(后面的流程与公平锁加锁失败流程类似:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
ReentrantLock非公平锁执行流程图:
四、synchronized 和 ReentrantLock 区别
synchronized 和 ReentrantLock 的异同,它们之间的相同点如下:
- 都可以用于实现线程间的同步访问;
- 两者都是可重入锁,即一个线程能够对资源重复加锁;
其不同点如下:
-
同步实现机制不同:
synchronized
通过Java
对象关联的Monitor
监视器实现(不考虑偏向锁、轻量级锁);ReentrantLock
通过CAS
、AQS
和LockSupport
等共同实现;
-
可见性实现机制不同:
synchronized
依赖JVM
内存模型保证包含共享变量的多线程内存可见性。ReentrantLock
通过ASQ
中volatile
类型的state
同步状态值保证包含共享变量的多线程内存可见性。
-
使用方式不同:
synchronized
可以用于修饰实例方法(锁住实例对象)、静态方法(锁住类对象)、同步代码块(指定的锁对象)。ReentrantLock
需要显式地调用lock
方法,并在finally
块中释放。
-
功能丰富程度不同:
synchronized
只提供最简单的加锁。ReentrantLock
提供定时获取锁、可中断获取锁、Condition
(提供await
、signal
等方法)等特性。
-
锁类型不同:
synchronized
只支持非公平锁。ReentrantLock
提供公平锁和非公平锁实现。但非公平锁相比于公平锁效率较高。
一般来说,仅当需要使用 ReentrantLock
提供的其他特性时,例如:可中断的、可定时的、可轮询的、公平地获取锁等,才考虑使用 ReentrantLock
。否则应该使用 synchronized
,简单方便。
参考资料