一种可重入的互斥锁,经由Java5引入,支持一个线程对资源的重复加锁。它和synchronized语句和方法访问的隐式监视器锁,有相同的基本行为和语义,但是功能更强大。之所以存在synchronized这种内置锁功能,还要新增Lock接口,是因为手动进行锁获取、释放,获取锁A、B,释放A、B,再去获取C、D…使用Lock会容易一些。
public class ReentrantLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = 7373984872572414699L;
/** Synchronizer providing all implementation mechanics */
private final 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)) {
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 object for non-fair locks
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
/**
* Sync object for fair locks
*/
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) {
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;
}
}
//默认非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
//fair传入true,采取公平锁策略
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
public void lock() {
sync.lock();
}
//这里省略了一部分...
public void unlock() {
sync.release(1);
}
public Condition newCondition() {
return sync.newCondition();
}
//省略...
}
这里截取了部分ReentrantLock源码,我们可以看到这里采用了组合模式把lock和unlock委托给同步器完成
显式调用,获取同步锁、释放同步锁即可
ReentrantLock lock = new ReentrantLock();//默认参数false,不公平锁
Condition condition = lock.newCondition();
lock.lock();//若被其他资源锁定,会在此等待锁释放,达到暂停效果
try {
while(条件判断表达式){
condition.wait();
}
//do something
}finally {
lock.unlock();
}
//这么写和上面一个效果
public synchronized void method(){}
锁对象,在java中锁是控制多个线程访问共享资源的方式,一个锁能防止多个线程同时访问共享资源(但有的锁就允许多个线程并发访问,如读写锁)。在Lock接口出现之前,是靠synchronized关键字来实现锁的。但是Java5中的并发包里新增了Lock接口实现锁的功能,提供了与synchronized关键字类似的同步功能,只是在使用时需要显示的获取、释放锁。虽然缺少synchronized那样隐式释放锁的便捷,但是却拥有了锁获取、释放的可操作性,并中断的获取锁以及超时获取锁等多种synchronized不具备的同步特性。
Lock接口主要方法:
void lock();//执行此方法时,若锁处于空闲状态,当前线程将获取到锁。否则,就禁用当前线程知道当前线程获取到锁
boolean tryLock();//如果锁可用,就获取并返回true。否则就返回false,但是当前线程不会被禁用,当前线程还可以继续执行代码。
void unLock();//当前线程将释放持有的锁,且锁只能由持有者释放。若某线程未持有锁,就执行该方法,会有异常
Condition newCondition();//条件对象,获取等待通知的组件。与当前锁绑定,当前线程只有获取了锁,才能调用该组件的await()方法
public class Ticket implements Runnable {
private int num = 100;//假设一共有100张票
ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
Thread.sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
}
lock.lock();
if (num>0){
System.out.println(Thread.currentThread().getName()+"卖了"+num--);
}
lock.unlock();
}
}
}
当一个线程得到一个对象时,再次请求该对象锁时是可以再次得到该对象的锁。Java里面的内置锁(synchronized)和Lock(ReentrantLock)都是可重入的。
在公平锁和非公平锁中,都对重入锁进行了实现。
if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
synchronized的重入锁特性:
public class SynchronizedTest {
public void method1(){
synchronized (SynchronizedTest.class){
System.out.println("方法1获取到的对象锁运行了");
method2();
}
}
public void method2() {
synchronized (SynchronizedTest.class){
System.out.println("方法1调用的方法2重入锁,也运行了");
}
}
public static void main(String []args){
new SynchronizedTest().method1();
}
}
ReentrantLock的重入锁特性:
public class ReentrantLockTest {
private Lock lock = new ReentrantLock();
public void method1(){
lock.lock();
try {
System.out.println("方法1获取到了ReentrantLock锁运行了");
method2();
}finally {
lock.unlock();
}
}
public void method2(){
lock.lock();
try {
System.out.println("方法1调用方法2冲入ReentrantLock业运行了");
}finally {
lock.unlock();
}
}
public static void main(String []args){
new ReentrantLockTest().method1();
}
}
在非公平锁中,每当线程执行lock方法时,都尝试利用CAS把state从0设置成1。那Doug lea又是怎么实现锁的非公平性的呢?假设:
(1)持有锁的线程A正在running,队列中的BCDEF被挂起等待被唤醒;
(2)在某一刻,线程A执行unlock,唤醒线程B;
(3)同时线程G执行lock,会怎么样呢?B、G拥有相同的获取锁的优先级,同时执行CAS指令竞争锁。如果G成功了,B就得被挂起等待重新唤醒;
即使B等了很久,在和G竞争锁的斗争中失败了,世道如此不公!
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
这里来描述一下非公平锁的竞争过程:
(1)线程A、B同时执行CAS指令,假设A成功,B失败,线程A成功获取到锁,并把同步器中的exclusiveOwnerThread设置为线程A
(2)失败的B,在nofairTryAcquire方法中,会尝试再次获取锁。Doug lea会尝试在多处重新获取锁,应该是在这段时间内,如果线程A释放锁,线程B就可以不用挂起而直接获取到锁。
在公平锁中,每当线程执行lock方法,如果同步器的队列中有线程再等待,就直接加入到队列中。假设:
(1)持有锁的线程A正在running,队列中的BCDEF挂起等待被唤醒;
(2)线程G执行lock方法,由于队列中有BCDEF在等待,所以G直接加入到队列中;
每个线程获取到锁的过程是公平的,等待时间最长的线程会最先被唤醒而获取锁。
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) {
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;
}
}
Condition在很大程度上是为了解决Object.wait/notify/notifyAll难以使用的问题。
public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;
/** First node of condition queue. */
private transient Node firstWaiter;
/** Last node of condition queue. */
private transient Node lastWaiter;
//省略...
public final void signal(){}
public final void signalAll(){}
public final void awaitUninterruptibly(){}
public final void await() throws InterruptedException{}
}
1.在synchronized中,所有线程都在同一个Object的条件队列上等待。而ReentrantLock中每个Condition都维护一个条件队列。
2.每一个Lock可以有任意数据的Condition对象,Condition是和Lock绑定的,所有就有Lock的公平特性:如果是公平锁,线程为按照FIFO的顺序从Condition.await中释放;如果是非公平锁,那后续的锁竞争就不保证FIFO的顺序了。
3.Condition接口的定义方法,await对应Object.wait,signal对应Object.notify,signalAll对应Object.notifyAll。Condition的接口改变名称,就是为了避免和Object中的wait/notify/notifyAll在语义和使用上混淆
1.ReentrantLock提供了内置锁类似的功能和内存语义
2.ReentrantLock还提供了定时的锁等待、可中断的锁等待、公平性以及实现非块结构的加锁、Condition。对线程的等待、唤醒更加灵活,一个ReentrantLock可以有多个Condition实例,故更有扩展性。ReentrantLock需要显式的获取锁,并在finally中释放。
3.ReentrantLock在性能上优于synchronized,那为何不放弃内置锁?之前提过了,这里不再赘述
4.在JDK5中,内置锁比ReentrantLock多一个优点:在线程转储中能给出在哪些调用帧中获得了哪些锁,并能检测、识别发生死锁的线程。ReentrantLock的非块状特性意味着,获取锁的操作不能与特定的栈帧关联起来,然而内置锁却可以。
5.内置锁是JVM的内置属性,未来更可能提升synchronized的性能,例如对线程封闭的锁对象消除优化,增加锁粒度来消除内置锁的同步