ReentrantLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。ReenreantLock类的常用方法有:
ReentrantLock() : 创建一个ReentrantLock实例
ReentrantLock(boolean fair) 是否是公平锁,默认不是 【实际开发中 一般不开启公平】
lock() : 获得锁
unlock() : 释放锁
下面代码演示 ReentrantLock 实现的累加器
public class RentLack implements Runnable {
ReentrantLock lock = new ReentrantLock(); private int count = 0;
public int getCount()
{
return count;
}
@Override
public void run() { for (int i = 0; i < 10000; i++) {
lock.lock();
try {
count++;
} finally {
lock.unlock(); //释放锁一定要写在finally里面
}
} } public static void main(String[] args) throws Exception {
RentLack rentLack = new RentLack();
Thread t1 = new Thread(rentLack);
Thread t2 = new Thread(rentLack);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(rentLack.getCount());
}
}
原理解析 ----------
说道ReentrantLock,不得不谈AbstractQueuedSynchronized(AQS抽象的队列式的同步器),
AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch...。
-----深入了解AQS 请google相关信息
当我们调用 ReentrantLock lock = new ReentrantLock(); 看看具体是怎么做的?
private final Sync sync;
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
可以看到,它调用是 上面两个类 他们的关系如下
ReentrantLock实现了Lock接口,获取锁是通过lock方法来实现的。
那当我们调用lock的时候,具体都做了什么呢? 这里只分析不公平方式。
具体代码如下
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();
} ==========
下来看看 非公平的方式的tryAcquire final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//state字段,在ReentrantLock中表示锁被持有的次数,它是一个volatile类型的整型值,因此对它的修改可以保证其他线程可以看到
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;
}
执行流程如下
如果没有线程占有该资源,直接当前线程占有。
如果占用了,执行acquire(1)
- 调用tryAcquire()尝试直接去获取资源,如果成功则直接返回;
- 获取失败,则addWaiter()将该线程加入等待队列
- acquireQueued()使线程在等待队列中休息,有机会时(轮到自己,会被unpark())会去尝试获取资源。获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false。
4.自我中断
在tryAcquire方法中可以看到,同一个线程获得锁以后可以多次重入,但是需要多次unlock哈。tryAcquire执行流程如下
1、首先判断锁有没有被持有,如果被持有,就判断持有锁的线程是不是当前线程,如果不是就啥也不做,返回获取失败,如果是就增加重入数,返回成功获取;
2、如果锁没有被任何线程持有(c==0),直接将当前线程设置为锁的持有者。
======================
总结:关于 synchronized , ReentrantLock 究竟用谁的问题?
synchronized 能解决的问题,ReentrantLock 都能解决 。
在性能上,Jdk8 对synchronized进行了优化,性能上差别不大。