锁的基本概念
- 可重入锁
Reentrant 就是可重入的意思,如果锁具备可重入性,则称作为可重入锁。像synchronized和ReentrantLock都是可重入锁,可重入性在我看来实际上表明了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。举个简单的例子,当一个线程执行到某个synchronized方法时,比如说method1,而在method1中会调用另外一个synchronized方法method2,此时线程不必重新去申请锁,而是可以直接执行方法method2。
class MyClass {
public synchronized void method1() {
method2();
}
public synchronized void method2() {
}
}
上述代码中的两个方法method1和method2都用synchronized修饰了,假如某一时刻,线程A执行到了method1,此时线程A获取了这个对象的锁,而由于method2也是synchronized方法,假如synchronized不具备可重入性,此时线程A需要重新申请锁。但是这就会造成一个问题,因为线程A已经持有了该对象的锁,而又在申请获取该对象的锁,这样就会线程A一直等待永远不会获取到的锁。
- 公平锁,非公平锁
即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。
非公平锁即无法保证锁的获取是按照请求锁的顺序进行的。这样就可能导致某个或者一些线程永远获取不到锁。
在Java中,synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。
而对于ReentrantLock和ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。
在ReentrantLock中定义了2个静态内部类,一个是NotFairSync,一个是FairSync,分别用来实现非公平锁和公平锁。
- 共享锁,独占锁
共享锁,是可以多个线程获取到锁 (如读锁,可以多个线程一起读)
独占锁,从名字上就可以知道,一个线程获取了锁,其它线程就不能获取到锁,必须等锁释放了,才能可能获取到锁 (如 写锁)
在ReentrantReadWriteLock中定义了2个静态内部类,一个是ReadLock,一个是WriteLock,分别用来实现读锁和写锁。
1. ReentrantLock 锁的使用
在多线程编程中,有可能会出现同时访问同一个资源的情况,这种资源可以是各种类型的的资源:一个变量、一个对象、一个文件、一个数据库表等,而当多个线程同时访问同一个资源的时候,就会存在一个问题:
由于每个线程执行的过程是不可控的,所以很可能导致最终的结果与实际上的愿望相违背或者直接导致程序出错。
例如以下程序,分别起三个线程把 变量 i 加到300000,如果不加锁的话就会出错
public class LockTest {
ReentrantLock mLock = new ReentrantLock();
private int i = 0;
public static void main(String[] args) {
LockTest main = new LockTest();
main.done();
}
private void done() {
new Thread(new Runnable() {
@Override
public void run() {
dosomeThings1();
}
},"thread-1").start();
new Thread(new Runnable() {
@Override
public void run() {
dosomeThings1();
}
},"thread-2").start();
new Thread(new Runnable() {
@Override
public void run() {
dosomeThings1();
}
},"thread-3").start();
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ " i== "+i);
}
private void dosomeThings1() {
for (int j = 0; j < 100000; j++) {
i++;
}
System.out.println(Thread.currentThread().getName()+ " i== "+i);
}
private void dosomeThings2() {
mLock.lock();
try {
for (int j = 0; j < 100000; j++) {
i++;
}
System.out.println(Thread.currentThread().getName()+ " i== "+i);
} catch (Exception e) {
e.printStackTrace();
} finally {
mLock.unlock();
}
}
}
dosomeThings1 的执行结果,没有确定性,每次执行结果都不一样
dosomeThings2 的执行结果确定,符合我们的要求
ReentrantLock 能够出现正确的结果是因为每次只有一个线程进行操作 i 变量,所以就不会出现 i 出错的情况,虽然上述例子无实际意义,其实效率还没一个线程直接加到300000 效率高,没有体现多线程编程的优势,不过这篇博文的主要是说明 ReentrantLock 如何确保同一时刻只有一个线程操作共享变量 i 的.
2. ReentrantLock 源码分析
ReentrantLock 有两个构造函数,一个默认构造函数创建非公平,一个传入一个 boolean 变量,若为 ture 则创建公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
NonfairSync 非公平锁的 类继承结构如下
static final class NonfairSync extends Sync {}
abstract static class Sync extends AbstractQueuedSynchronizer{}
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer{}
mLock.lock();
public void lock() {
sync.lock(); // 调用 class NonfairSync类中的 lock 方法
}
在 class NonfairSync 中
final void lock() {
/*
由于默认值为 0,所以第一个线程 thread-1 进入 dosomeThings 调用 mLock.lock(); 的时候与预期相符,
并把 state的值更新为 1,返回值为 true,并设置当前获取锁的线程为 thread-1 ,然后 thread-1就可以进行操作 共享变量 i;
当线程2 thread-2 ,线程3 thread-3 进入 dosomeThings (若线程 thread-1 没执行完并没有释放锁) 的时候调用 mLock.lock();方法是时候
由于线程 thread-1 state的值更新为 1,与预期的 0值不符返回 false . 调用 acquire(1); 方法
*/
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
在 class AbstractQueuedSynchronizer 中
state 是 AbstractQueuedSynchronizer 类的一个成员变量,默认为 0,
stateOffset 是state在 AbstractQueuedSynchronizer类中的内存偏移值,可以通过 unsafe对象直接对 state 进行赋值操作.
关于 Unsafe ,CAS的内容请自行搜索相关资料.
/**
* The synchronization state.
*/
private volatile int state;
stateOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("state"));
/*
CAS的语义是“我认为V的值应该为A,如果是,那么将V的值更新为B,否则不修改并告诉V的值实际为多少”,
CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,
而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做
*/
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
当 thread-1 获取到锁之后未释放的话,线程 thread-2 ,线程 thread-3 ,就会进入 acquire
/*
tryAcquire 返回 false,若等待的线程没有中断 acquireQueued 返回 false
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
在 class NonfairSync 中 tryAcquire 方法
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
/*获取当前 state 值由于 thread-1 线程获取锁, c ==1, 此时判断为 false
如果线程 1 执行完释放锁, c == 0,线程 thread-2 ,线程 thread-3 会进行再次获取锁*/
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) { // 由于 thread-1 线程获取锁此时为 false
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
在 class AbstractQueuedSynchronizer 中 addWaiter(Node.EXCLUSIVE), arg)
/*
共享锁,是可以多个线程获取到锁 (如读锁,可以多个线程一起读)
*/
static final Node SHARED = new Node(); // 标记构建的新的节点 Node 是共享锁
/*
独占锁,从名字上就可以知道,一个线程获取了锁,其它线程就不能获取到锁,必须等锁释放了,才能可能获取到锁 (如 写锁)
*/
static final Node EXCLUSIVE = null; // 标记构建的新的节点 Node 是独占锁
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode); // 当前的线程和标记 EXCLUSIVE 构建一个新的 Node对象
// Try the fast path of enq; backup to full enq on failure
Node pred = tail; // 刚开始是 head,tail 头尾节点 Node 都是空
if (pred != null) {
// 若已经初始化过等待的链表,则直接加入到链表的尾部,直接返回节点,(若线程2已经初始化过链表,线程3 会执行)
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node); // 将代表thread-2 节点 Node 节点入链表 (线程2 未初始化 链表会执行)
return node;
}
在 class AbstractQueuedSynchronizer 中 enq(node);
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize // 刚开始是 head,tail 头尾节点的 Node 都是空
if (compareAndSetHead(new Node())) // 新建一个 Node 为头节点,初始化一个链表结构
tail = head; // 头尾节点都为同一个 Node 为头节点对象链表为空
} else { // for 循环第二次执行时, tail 已经不为空了,若尾节点不为空,就将线程thread-2 加入链表的尾部
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
在 class AbstractQueuedSynchronizer 中 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)),
在addWaiter方法中构建完节点之后机会返回 Node对象 执行 acquireQueued 方法
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// for 循环一直把所有要获取锁的线程阻塞起来,并且一直尝试获取锁并且检查线程是否被中断
final Node p = node.predecessor();
/*
获取 node 节点的前一个节点,若只有一个 thread-2 等待, p == head 为 true,
(若为代表 thread-3 的Node 节点返回的是 thread-2的 node ),
不管是哪个 node 然后再次去获取锁,此时线程1 获取到锁 然后返回 false
*/
if (p == head && tryAcquire(arg)) {
// 除非前一个节点是头节点并且是能够获取到锁才能跳出循环去执行
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted; //返回线程是否中断
}
// 若获取不到锁,或者 thread-2 的前一个节点不是 头节点,则执行
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
如果在执行过程中,等待的线程获取到锁,执行
private void setHead(Node node) { // 设置头节点为当前节点
head = node;
node.thread = null;
node.prev = null;
}
Node 定义的几个状态
/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
在 class AbstractQueuedSynchronizer 中 shouldParkAfterFailedAcquire(p, node)
// 判断获取锁失败后是否需要进行阻塞
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
/*
默认值为 0 ,执行 compareAndSetWaitStatus,设置waitStatus 为 Node.SIGNAL ,
返回为 false,第二次进入时,直接返回为 true */
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
// 若 waitStatus只有取消状态 CANCELLED是大于 0 的,取消则移除此节点
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
在 class AbstractQueuedSynchronizer 中 parkAndCheckInterrupt() 方法
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this); // 阻塞当前线程
return Thread.interrupted();// 获取线程是否被中断状态
}
当线程thread-2 ,thread-3没有获取到锁时,就会进入阻塞状态,然后获取到锁的线程 thread-1执行完 i++ 到100000,就会执行 finally 释放锁 mLock.unlock();
public void unlock() {
sync.release(1);
}
在 class AbstractQueuedSynchronizer 中 release(1);
public final boolean release(int arg) {
// tryRelease 返回 true
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
// 获取等待锁的线程的的头节点,在shouldParkAfterFailedAcquire中waitStatus已设置为 SIGNAL
unparkSuccessor(h);
return true;
}
return false;
}
在 class Sync 中
protected final boolean tryRelease(int releases) {
int c = getState() - releases; // getState 为 1 ,release 为 1 ,c =0
if (Thread.currentThread() != getExclusiveOwnerThread()) // 如果当前的线程不是持有锁的线程,则会抛出异常
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null); // 设置当前获取锁的线程为 null
}
setState(c); // 设置 state 为 0;
return free;
}
在 class AbstractQueuedSynchronizer 中 unparkSuccessor(h); 唤醒等待锁的线程,唤醒其他线程就可以获得锁,然后就可以进入 for 循环操作共享变量 i
/**
* Wakes up node's successor, if one exists.
*
* @param node the node
*/
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0); // 把 SIGNAL状态 设置 0,初始状态
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) { // 如果 node 的下一个节点 为空,或者 被取消
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
// 则从链表从尾部开始遍历到头部,若节点node 的状态不为 CANCELLED 和 0
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread); //唤醒线程
}