ReentrantLock,意思是“可重入锁”,关于可重入锁的概念在下一节讲述。ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法。下面通过一些实例看具体看一下如何使用ReentrantLock。
1. Lock代码实例:
你可以在[github][1]中看到。
1.1 lock();
首先,我们尝试写一个错误的代码:
public class ReentrantLockWrongSample {
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList<Integer>();
WrongSampleThread thread1 = new WrongSampleThread(arrayList);
WrongSampleThread thread2 = new WrongSampleThread(arrayList);
new Thread(thread1).start();
new Thread(thread1).start();
new Thread(thread2).start();
new Thread(thread2).start();
}
}
public class WrongSampleThread implements Runnable {
private ArrayList<Integer> arrayList;
public WrongSampleThread(ArrayList<Integer> arrayList) {
this.arrayList = arrayList;
}
@Override
public void run() {
Lock lock = new ReentrantLock(); //注意这个地方
lock.lock();
try {
System.out.println(Thread.currentThread() + "得到了锁");
for (int i = 0; i < 5; i++) arrayList.add(i);
Thread.sleep(100);
} catch (Exception e) {
} finally {
System.out.println(Thread.currentThread() + "释放了锁");
lock.unlock();
}
}
}
output:
Thread[Thread-0,5,main]得到了锁
Thread[Thread-3,5,main]得到了锁
Thread[Thread-2,5,main]得到了锁
Thread[Thread-1,5,main]得到了锁
Thread[Thread-1,5,main]释放了锁
Thread[Thread-0,5,main]释放了锁
Thread[Thread-3,5,main]释放了锁
Thread[Thread-2,5,main]释放了锁
第二个线程怎么会在第一个线程释放锁之前得到了锁?原因在于,在insert方法中的lock变量是局部变量,每个线程执行该方法时都会保存一个副本,那么理所当然每个线程执行到lock.lock()处获取的是不同的锁,所以就不会发生冲突。 所以我们只需要把Lock设置为类的属性:
public class ReentrantLockRightSample {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
Lock lock = new ReentrantLock(); //注意这个地方
RightSampleThread thread1 = new RightSampleThread(lock, list);
RightSampleThread thread2 = new RightSampleThread(lock, list);
new Thread(thread1).start();
new Thread(thread2).start();
}
}
public class RightSampleThread implements Runnable {
private Lock lock;
private List<Integer> list;
public RightSampleThread(Lock lock, List<Integer> list) {
this.lock = lock;
this.list = list;
}
@Override
public void run() {
lock.lock();
try {
System.out.println(Thread.currentThread() + "得到了锁");
for (int i = 0; i < 5; i++) list.add(i);
Thread.sleep(200);
} catch (Exception e) {
// TODO: handle exception
} finally {
System.out.println(Thread.currentThread() + "释放了锁");
lock.unlock();
}
}
}
output:
Thread[Thread-0,5,main]得到了锁
Thread[Thread-0,5,main]释放了锁
Thread[Thread-1,5,main]得到了锁
Thread[Thread-1,5,main]释放了锁
1.2 tryLock()
public class ReentrantLockTryLockLearn {
public static void main(String[] args) {
List<Integer> list = new ArrayList<Integer>();
Lock lock = new ReentrantLock(); //注意这个地方
TryLockThread thread1 = new TryLockThread(list, lock);
TryLockThread thread2 = new TryLockThread(list, lock);
TryLockWithTimeThread thread3 = new TryLockWithTimeThread(list, lock);
new Thread(thread1).start();
new Thread(thread2).start();
new Thread(thread3).start();
}
}
public class TryLockThread implements Runnable {
private List<Integer> list;
private Lock lock;
public TryLockThread(List<Integer> list, Lock lock) {
this.list = list;
this.lock = lock;
}
@Override
public void run() {
if (lock.tryLock()) {
try {
System.out.println(Thread.currentThread() + "得到了锁");
for (int i = 0; i < 10; i++) {
list.add(i);
}
Thread.sleep(500);
} catch (Exception e) {
// TODO: handle exception
} finally {
System.out.println(Thread.currentThread() + "释放了锁");
lock.unlock();
}
} else {
System.out.println(Thread.currentThread() + "获取锁失败");
}
}
}
public class TryLockWithTimeThread implements Runnable {
private List<Integer> list;
private Lock lock;
public TryLockWithTimeThread(List<Integer> list, Lock lock) {
this.list = list;
this.lock = lock;
}
@Override
public void run() {
try {
if (lock.tryLock(5, TimeUnit.SECONDS)) {
try {
System.out.println(Thread.currentThread() + "得到了锁");
for (int i = 0; i < 10; i++) {
list.add(i);
}
Thread.sleep(100);
} catch (Exception e) {
// TODO: handle exception
} finally {
System.out.println(Thread.currentThread() + "释放了锁");
lock.unlock();
}
} else {
System.out.println(Thread.currentThread() + "获取锁失败");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
output:
Thread[Thread-0,5,main]得到了锁
Thread[Thread-1,5,main]获取锁失败
Thread[Thread-0,5,main]释放了锁
Thread[Thread-2,5,main]得到了锁
Thread[Thread-2,5,main]释放了锁
1.3 lockInterruptibly()
public class LockInterruptiblyLockLearn {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
LockInterruptiblyThread interruptiblyThread1 = new LockInterruptiblyThread(lock);
LockInterruptiblyThread interruptiblyThread2 = new LockInterruptiblyThread(lock);
Thread thread1 = new Thread(interruptiblyThread1);
Thread thread2 = new Thread(interruptiblyThread2);
thread1.start();
thread2.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread2.interrupt();
}
}
public class LockInterruptiblyThread implements Runnable {
private Lock lock;
public LockInterruptiblyThread(Lock lock) {
this.lock = lock;
}
@Override
public void run() {
try {
//注意,如果需要正确中断等待锁的线程,必须将获取锁放在外面,然后将InterruptedException抛出
lock.lockInterruptibly();
try {
System.out.println(Thread.currentThread() + "得到了锁");
long startTime = System.currentTimeMillis();
for (; ; ) {
if (System.currentTimeMillis() - startTime >= 5000) break;
//插入数据
}
} finally {
System.out.println(Thread.currentThread().getName() + "执行finally");
lock.unlock();
System.out.println(Thread.currentThread() + "释放了锁");
}
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(Thread.currentThread().getName() + "被中断");
}
}
}
output:
Thread[Thread-0,5,main]得到了锁
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
at com.lock.reentrantLock.reentrantLockApi.thread.LockInterruptiblyThread.run(LockInterruptiblyThread.java:38)
at java.lang.Thread.run(Thread.java:748)
Thread-1被中断
Thread-0执行finally
Thread[Thread-0,5,main]释放了锁
2. Condition 例子:
Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set (wait-set)。
其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。
在Condition中,用await()替换wait(),用signal()替换notify(),用signalAll()替换notifyAll(),传统线程的通信方式,Condition都可以实现,这里注意,Condition是被绑定到Lock上的,要创建一个Lock的Condition必须用newCondition()方法。
这样看来,Condition和传统的线程通信没什么区别,Condition的强大之处在于它可以为多个线程间建立不同的Condition。
你可以在[github][4]中发现源码。
首先我们建立一个ArrayBuffer
类,这个类中,我们设置了两个方法,read()
和put(String number)
。
write
方法是向队列中写入数据,read
方法是从队列中读取数据。
public class ArrayBuffer {
private Lock lock = new ReentrantLock();
private Condition write;
private Condition read;
public ArrayBuffer() {
write = lock.newCondition();
read = lock.newCondition();
}
private String[] array = new String[30];
private int putPoint, takePoint;
private int count = 0;
public String read() {
lock.lock();
try {
if (count == 0) {
try {
System.out.println("buffer is empty");
write.signal();
read.await();
} catch (InterruptedException e) {e.printStackTrace();}
}
System.out.println("takePoint: " + takePoint);
String temp = array[takePoint()];
count--;
takePoint++;
return temp;
}finally {
lock.unlock();
}
}
public void put(String number) {
lock.lock();
try{
if (count>=array.length){
try {
System.out.println("buffer is full.");
read.signal();
write.await();
System.out.println("count: " + count);
} catch (InterruptedException e) {e.printStackTrace();}
}
array[putPoint()] = number;
count++;
System.out.println("putPoint: " + putPoint);
putPoint++;
}finally {lock.unlock();}
}
private int putPoint() {
return putPoint%(array.length);
}
private int takePoint() {
return takePoint%(array.length);
}
}
其次,我们建立两个不同的thread,分别读取和写入。
public class ReadThread implements Runnable {
private ArrayBuffer buffer;
public ReadThread(ArrayBuffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
System.out.println("read running.");
for (int i =0;i<500;i++) {
System.out.println("read result: " + buffer.read());
}
}
}
public class WriteThread implements Runnable {
private ArrayBuffer buffer;
public WriteThread(ArrayBuffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
System.out.println("write running");
for (int i =0;i<500;i++) {
buffer.put(i+"");
}
}
}
测试类
public class ConditionOnReadWriteLearn {
public static void main(String[] args) {
ArrayBuffer buffer = new ArrayBuffer();
ReadThread read = new ReadThread(buffer);
WriteThread write = new WriteThread(buffer);
new Thread(write).start();
new Thread(read).start();
}
}
output:
read running.
write running
putPoint: 0
takePoint: 0
read result: 0
putPoint: 1
···
putPoint: 9
putPoint: 10
takePoint: 1
read result: 1
putPoint: 11
···
putPoint: 31
buffer is full.
takePoint: 2
read result: 2
···
takePoint: 31
read result: 31
buffer is empty
count: 0
putPoint: 32
putPoint: 33
putPoint: 34
···
ReentrantLock源码分析
类的继承关系
ReentrantLock实现了Lock接口,Lock接口中定义了lock与unlock相关操作,并且还存在newCondition方法,表示生成一个条件。
public class ReentrantLock implements Lock, java.io.Serializable
类的内部类
ReentrantLock总共有三个内部类,并且三个内部类是紧密相关的,下面先看三个类的关系。
说明: ReentrantLock类内部总共存在Sync、NonfairSync、FairSync三个类,NonfairSync与FairSync类继承自Sync类,Sync类继承自AbstractQueuedSynchronizer抽象类。下面逐个进行分析。
- Sync类
Sync类的源码如下:
abstract static class Sync extends AbstractQueuedSynchronizer {
// 序列号
private static final long serialVersionUID = -5179523762034025860L;
// 获取锁
abstract void lock();
// 非公平方式获取
final boolean nonfairTryAcquire(int acquires) {
// 当前线程
final Thread current = Thread.currentThread();
// 获取状态
int c = getState();
if (c == 0) { // 表示没有线程正在竞争该锁
if (compareAndSetState(0, acquires)) { // 比较并设置状态成功,状态0表示锁没有被占用
// 设置当前线程独占
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;
}
// 试图在共享模式下获取对象状态,此方法应该查询是否允许它在共享模式下获取对象状态,如果允许,则获取它
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类存在如下方法和作用如下。
- 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);
}
}
说明: 从lock方法的源码可知,每一次都尝试获取锁,而并不会按照公平等待的原则进行等待,让等待时间最久的线程获得锁。
- FairSyn类
FairSync类也继承了Sync类,表示采用公平策略获取锁,其实现了Sync类中的抽象lock方法,源码如下:
// 公平锁
static final class FairSync extends Sync {
// 版本序列化
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
// 以独占模式获取对象,忽略中断
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
// 尝试公平获取锁
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;
}
}
说明: 跟踪lock方法的源码可知,当资源空闲时,它总是会先判断sync队列(AbstractQueuedSynchronizer中的数据结构)是否有等待时间更长的线程,如果存在,则将该线程加入到等待队列的尾部,实现了公平获取原则。其中,FairSync类的lock的方法调用如下,只给出了主要的方法。
说明: 可以看出只要资源被其他线程占用,该线程就会添加到sync queue中的尾部,而不会先尝试获取资源。这也是和Nonfair最大的区别,Nonfair每一次都会尝试去获取资源,如果此时该资源恰好被释放,则会被当前线程获取,这就造成了不公平的现象,当获取不成功,再加入队列尾部。
类的属性
ReentrantLock类的sync非常重要,对ReentrantLock类的操作大部分都直接转化为对Sync和AbstractQueuedSynchronizer类的操作。
public class ReentrantLock implements Lock, java.io.Serializable {
// 序列号
private static final long serialVersionUID = 7373984872572414699L;
// 同步队列
private final Sync sync;
}
类的构造函数
- ReentrantLock()型构造函数
默认是采用的非公平策略获取锁
public ReentrantLock() {
// 默认非公平策略
sync = new NonfairSync();
}
- ReentrantLock(boolean)型构造函数
可以传递参数确定采用公平策略或者是非公平策略,参数为true表示公平策略,否则,采用非公平策略:
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
核心函数分析
通过分析ReentrantLock的源码,可知对其操作都转化为对Sync对象的操作,由于Sync继承了AQS,所以基本上都可以转化为对AQS的操作。如将ReentrantLock的lock函数转化为对Sync的lock函数的调用,而具体会根据采用的策略(如公平策略或者非公平策略)的不同而调用到Sync的不同子类。
所以可知,在ReentrantLock的背后,是AQS对其服务提供了支持,由于之前我们分析AQS的核心源码,遂不再累赘。下面还是通过例子来更进一步分析源码。
示例分析
公平锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class MyThread extends Thread {
private Lock lock;
public MyThread(String name, Lock lock) {
super(name);
this.lock = lock;
}
public void run () {
lock.lock();
try {
System.out.println(Thread.currentThread() + " running");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
lock.unlock();
}
}
}
public class AbstractQueuedSynchronizerDemo {
public static void main(String[] args) throws InterruptedException {
Lock lock = new ReentrantLock(true);
MyThread t1 = new MyThread("t1", lock);
MyThread t2 = new MyThread("t2", lock);
MyThread t3 = new MyThread("t3", lock);
t1.start();
t2.start();
t3.start();
}
}
运行结果(某一次):
Thread[t1,5,main] running
Thread[t2,5,main] running
Thread[t3,5,main] running
说明: 该示例使用的是公平策略,由结果可知,可能会存在如下一种时序。
说明: 首先,t1线程的lock操作 -> t2线程的lock操作 -> t3线程的lock操作 -> t1线程的unlock操作 -> t2线程的unlock操作 -> t3线程的unlock操作。根据这个时序图来进一步分析源码的工作流程。
- t1线程执行lock.lock,下图给出了方法调用中的主要方法。
说明: 由调用流程可知,t1线程成功获取了资源,可以继续执行。
- t2线程执行lock.lock,下图给出了方法调用中的主要方法。
说明: 由上图可知,最后的结果是t2线程会被禁止,因为调用了LockSupport.park。
- t3线程执行lock.lock,下图给出了方法调用中的主要方法。
说明: 由上图可知,最后的结果是t3线程会被禁止,因为调用了LockSupport.park。
- t1线程调用了lock.unlock,下图给出了方法调用中的主要方法。
说明: 如上图所示,最后,head的状态会变为0,t2线程会被unpark,即t2线程可以继续运行。此时t3线程还是被禁止。
- t2获得cpu资源,继续运行,由于t2之前被park了,现在需要恢复之前的状态,下图给出了方法调用中的主要方法。
说明: 在setHead函数中会将head设置为之前head的下一个结点,并且将pre域与thread域都设置为null,在acquireQueued返回之前,sync queue就只有两个结点了。
- t2执行lock.unlock,下图给出了方法调用中的主要方法。
说明: 由上图可知,最终unpark t3线程,让t3线程可以继续运行。
- t3线程获取cpu资源,恢复之前的状态,继续运行。
说明: 最终达到的状态是sync queue中只剩下了一个结点,并且该节点除了状态为0外,其余均为null。
- t3执行lock.unlock,下图给出了方法调用中的主要方法。
说明: 最后的状态和之前的状态是一样的,队列中有一个空节点,头节点为尾节点均指向它。