jdk1.5之前,我们对代码加锁(实际是对象加锁),都是采用Synchronized关键字来处理,jdk1.5及以后的版本中,并发编程大师Doug Lea在concurrrent包中提供了Lock机制。两种机制在性能上目前的jdk版本都差不多,Synchronized作为jvm的关键字,是在jvm层面实现的锁机制,而Lock机制是在java语言这个级别实现的锁机制,其实锁的核心原理都是为某个对象加锁。本文中将以Lock机制的源代码来分析锁机制的原理和实现,后面的demo代码也将按照Lock锁来展开。
demo源码:https://github.com/mantuliu/javaAdvance 中的类 Lesson3CoarseGrainedLock和Lesson3FinedGrainedLock
下面的斜体段落是java api中对Lock的描述:
Lock
实现提供了比使用 synchronized
方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition
对象。
锁是控制多个线程对共享资源进行访问的工具。通常,锁提供了对共享资源的独占访问。一次只能有一个线程获得锁,对共享资源的所有访问都需要首先获得锁。不过,某些锁可能允许对共享资源并发访问,如 ReadWriteLock
的读取锁。
synchronized
方法或语句的使用提供了对与每个对象相关的隐式监视器锁的访问,但却强制所有锁获取和释放均要出现在一个块结构中:当获取了多个锁时,它们必须以相反的顺序释放,且必须在与所有锁被获取时相同的词法范围内释放所有锁。
虽然 synchronized
方法和语句的范围机制使得使用监视器锁编程方便了很多,而且还帮助避免了很多涉及到锁的常见编程错误,但有时也需要以更为灵活的方式使用锁。例如,某些遍历并发访问的数据结果的算法要求使用 “hand-over-hand” 或 “chain locking”:获取节点 A 的锁,然后再获取节点 B 的锁,然后释放 A 并获取 C,然后释放 B 并获取 D,依此类推。Lock
接口的实现允许锁在不同的作用范围内获取和释放,并允许以任何顺序获取和释放多个锁,从而支持使用这种技术。
随着灵活性的增加,也带来了更多的责任。不使用块结构锁就失去了使用 synchronized
方法和语句时会出现的锁自动释放功能。在大多数情况下,应该使用以下语句:
Lock l = ...; l.lock(); try { // access the resource protected by this lock } finally { l.unlock(); }
锁定和取消锁定出现在不同作用范围中时,必须谨慎地确保保持锁定时所执行的所有代码用 try-finally 或 try-catch 加以保护,以确保在必要时释放锁。
Lock
实现提供了使用 synchronized
方法和语句所没有的其他功能,包括提供了一个非块结构的获取锁尝试 (tryLock()
)、一个获取可中断锁的尝试 (lockInterruptibly()
) 和一个获取超时失效锁的尝试 (tryLock(long, TimeUnit)
)。
Lock
类还可以提供与隐式监视器锁完全不同的行为和语义,如保证排序、非重入用法或死锁检测。如果某个实现提供了这样特殊的语义,则该实现必须对这些语义加以记录。
注意,Lock
实例只是普通的对象,其本身可以在 synchronized
语句中作为目标使用。获取 Lock
实例的监视器锁与调用该实例的任何 lock()
方法没有特别的关系。为了避免混淆,建议除了在其自身的实现中之外,决不要以这种方式使用 Lock
实例。
除非另有说明,否则为任何参数传递 null
值都将导致抛出 NullPointerException
。
内存同步
所有 Lock
实现都必须 实施与内置监视器锁提供的相同内存同步语义,如 The Java Language Specification, Third Edition (17.4 Memory Model) 中所描述的:
- 成功的
lock
操作与成功的 Lock 操作具有同样的内存同步效应。 - 成功的
unlock
操作与成功的 Unlock 操作具有同样的内存同步效应。
不成功的锁定与取消锁定操作以及重入锁定/取消锁定操作都不需要任何内存同步效果。
实现注意事项
三种形式的锁获取(可中断、不可中断和定时)在其性能特征、排序保证或其他实现质量上可能会有所不同。而且,对于给定的 Lock
类,可能没有中断正在进行的 锁获取的能力。因此,并不要求实现为所有三种形式的锁获取定义相同的保证或语义,也不要求其支持中断正在进行的锁获取。实现必需清楚地对每个锁定方法所提供的语义和保证进行记录。还必须遵守此接口中定义的中断语义,以便为锁获取中断提供支持:完全支持中断,或仅在进入方法时支持中断。
由于中断通常意味着取消,而通常又很少进行中断检查,因此,相对于普通方法返回而言,实现可能更喜欢响应某个中断。即使出现在另一个操作后的中断可能会释放线程锁时也是如此。实现应记录此行为。
Lock接口的实现类有:ReentrantLock, ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock;本文将以ReentrantLock的源码来分析内部实现原理:
a.lock()方法
public void lock() { sync.lock();//调用sync对象的lock()方法 }
下面看看sync对象的创建过程及它的lock()方法
public ReentrantLock() {
sync = new NonfairSync();//我们发现实现sync对象的类是NonfairSync
}
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() {
/*调用unsafe的compareAndSwapInt方法(原子操作),如果Lock对象存储的对象值是0(与第一个参数值相同),则将此值设置为1(与第二个参数值相同),反之,则返回失败*/
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());//设置当前线程为锁的持有者
else
acquire(1);//当前线程等待其它线程释放锁
}
}
b.tryLock()方法:仅在调用时锁为空闲状态才获取该锁
public boolean tryLock() { return sync.nonfairTryAcquire(1);//尝试获取锁 } final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread();//获取到当前线程 int c = getState();//获取锁的状态 if (c == 0) {//0表示锁未被占用 if (compareAndSetState(0, acquires)) {//调用unsafe的compareAndSwapInt方法(原子操作),标识此锁为已经占有状态 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; }
c.tryLock(long time, TimeUnit unit)方法,介于lock()和tryLock()方法之间,如果锁已经被其它线程占用,则会等待一段时间
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1, unit.toNanos(timeout)); } public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); return tryAcquire(arg) || //调用NonfairSync的tryAcquire(方法),与上面的tryLock()方法调用的nonfairTryAcquire()一致 doAcquireNanos(arg, nanosTimeout); }
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
long lastTime = System.nanoTime();//按纳秒返回当前时间
final Node node = addWaiter(Node.EXCLUSIVE);//创建一个节点并入队列来等待锁
boolean failed = true;
try {
for (;;) {//一直循环
final Node p = node.predecessor();//返回当前节点的前一个等待节点
if (p == head && tryAcquire(arg)) {//如果前一个节点已经是head节点,则试图去获取锁,如果获取到了,则执行下面的代码
setHead(node);//设置当前节点为队列中的头节点
p.next = null; // help GC
failed = false;
return true;
}
if (nanosTimeout <= 0)
return false;//超时返回失败
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
long now = System.nanoTime();
nanosTimeout -= now - lastTime;
lastTime = now;
if (Thread.interrupted())//线程被中断了
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
d.lockInterruptibly()方法,可以相应线程中断的去获取锁,如果线程被中断了,则抛出异常
public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1);//调用acquireInterruptibly } public final void acquireInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted())//如果线程被中断了,则抛出异常 throw new InterruptedException(); if (!tryAcquire(arg))////调用NonfairSync的tryAcquire(方法),与上面的tryLock()方法调用的nonfairTryAcquire()一致 doAcquireInterruptibly(arg); }
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) && //检测线程是否被中断
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
e.unlock()方法,释放锁,通常只有持有锁的线程才能释放锁成功
public void unlock() { sync.release(1); } public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); 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;
}
上面的代码分析了ReentrantLock的非公平锁的实现,在实现的使用过程中,锁和线程是息息相关的,如果我们把线程比作高速公路上的车道,那么行驶在高速公路上的车辆就是我们实际的代码(操作),因为路况或其它原因,各条车道需要并线,为防止意外发生,在一定时间内只能有一条车道或几条路上的车可以通过(代码上了锁),为保证通行时间,有两点需要注意:1是车辆通过的速度要快(线程占用锁的时间要少),2是在高速公路的车道数足够多的情况下,要尽可能的将车道分成多个小组,各个小组内部交替通行,小组与小组之间不影响。总结起来,在使用锁的情况下,要想对系统的性能影响足够小:1.锁的粒度要足够小,尽量减少同时使用同一把锁的线程数量;2.加锁的线程执行时间要足够快。下面的demo从粒度的角度展示了锁对于性能的影响:
先来看看粗粒度的锁,100个线程锁同一个资源,每个线程执行1秒钟的情况:
package com.mantu.advance; import java.util.concurrent.locks.ReentrantLock; /** * blog http://www.cnblogs.com/mantu/ * github https://github.com/mantuliu/ * @author mantu * */ public class Lesson3CoarseGrainedLock implements Runnable{ public static ReentrantLock lock = new ReentrantLock(); public static void main(String args[]){ for(int i=0;i<100;i++){ Thread thread =new Thread(new Lesson3CoarseGrainedLock()); thread.start(); } } @Override public void run() { try { lock.lock();//100个线程使用同一个锁,每个时刻只能有一个线程执行 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally{ lock.unlock(); System.out.println("线程"+Thread.currentThread().getId()+"执行完毕"); } } }
再来看看细粒度锁的demo,每10个线程分为一个组,组内的线程才会存在资源竞争的情况,执行完代码后,会发现效率比上一个demo提升了非常多
package com.mantu.advance; import java.util.HashMap; import java.util.concurrent.locks.ReentrantLock; /** * blog http://www.cnblogs.com/mantu/ * github https://github.com/mantuliu/ * @author mantu * */ public class Lesson3FinedGrainedLock implements Runnable{ public static HashMap<Integer,ReentrantLock> lockMap = new HashMap<Integer,ReentrantLock>(); public static void main(String args[]){ init(); for(int i=0;i<100;i++){ Thread thread =new Thread(new Lesson3FinedGrainedLock()); thread.start(); } } public static void init(){ for(int i=0;i<10;i++){ lockMap.put(new Integer(i),new ReentrantLock()); } } @Override public void run() { ReentrantLock lock = Lesson3FinedGrainedLock.lockMap.get((int)(Thread.currentThread().getId())%10); try { lock.lock();//细粒度的锁,效率提高非常多 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally{ lock.unlock(); System.out.println("线程"+Thread.currentThread().getId()+"执行完毕"); } } }