概述
在java多线程同步过程中,除了使用synchronized关键字外,还在java.util.concurrent.lock包中提供了一些手动加锁的类。类关系如下:
常用的锁----ReentrantLock
概念
ReentrantLock类是java提供的给 同步代码块 加锁,保证线程同步的类。主要依赖的原理为volatile+CAS。
有哪些种类?
ReentrantLock为我们提供了公平锁和非公平锁两种,默认为非公平锁,若要使用公平锁,则只需要在构造函数中加入true即可。
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
怎么用?
下面列出ReetrantLock的基本用法:
package thread.sourcecode;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class T02_Lock {
ReentrantLock rLock=new ReentrantLock();
int i=0;
void testReentrantLock(){
//rLock.tryLock();
rLock.lock();
i++;
rLock.unlock();
}
public static void main(String[] args) {
T02_Lock t=new T02_Lock();
new Thread(t::testReentrantLock,"thread_0").start();
}
}
代码说明:在i++前,执行tryLock()或者lock()方法(两者存在区别),表示当前线程获得“锁”,执行完i++后,手动释放锁(必须释放),相当于synchronize的同步代码块;
源码分析
常用方法
//获得锁
void lock();
//尝试获得锁
boolean tryLock();
//尝试获得锁,并等待一段时间
boolean tryLock(long time, TimeUnit unit);
//尝试获得锁,可以被打断
void lockInterruptibly() throws InterruptedException;
//释放锁
void unlock();
//常见一个condition
Condition newCondition();
lock()
执行lock()方法,调用Sync中的acquire(1)方法,但是这个是abstract方法,实际上调用的是它的子类的实现方法。
在nofairSync中,首先调用AQS的compareAndSetState()方法,尝试获得“锁”,若成功,则设置当前线程为占有线程,若失败,则调用AQS的acquire()方法,会调用nonfairSync中的tryAcquire(1),并且将当前线程加入等待队列,并将当前线程中断。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
可以总结出执行流程图
tryLock()
调用过程
执行tryLock()方法,执行Sync的 nonfairTryAcquire(1)方法,源代码如下:
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;
}
可以看出,首先尝试获得锁,若成功,则将当前线程设置为独自占有线程,并返回true;若失败,则判断当前线程是否是占有线程,若是,则继续占有,若不是,则返回失败(false)。
总结执行流程图如下:
lockInterruptibly()
使用示例
package thread;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class T07_LockInterrupter {
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
try {
lock.lock();
System.out.println("t1 start...");
TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
System.out.println("interrupted");
} finally {
lock.unlock();
}
},"thread-1");
t1.start();
Thread t2 = new Thread(() -> {
try {
// lock.lock();
lock.lockInterruptibly();
System.out.println("t2 start...");
TimeUnit.SECONDS.sleep(5);
System.out.println("t2 end...");
} catch (InterruptedException e) {
System.out.println("interrupted");
} finally {
lock.unlock();
}
},"thread-2");
t2.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.interrupt();//打断t2线程的等待
}
}
进入方法内部,调用的sync的acquireInterruptibly(1)方法,而其核心是调动AQS的doAcquireInterruptibly(1)方法,调用流程如下:
doAcquireInterruptibly方法源代码如下:
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);
}
}
简要分析,当当前线程未获取到“锁”时,AQS将其加入等待队列,并且开始无限循环,不断调用tryAcquire()方法,若获得锁,则返回;若未获得锁,且被中断(如main方法中执行t2.interrupt()),则抛出异常,最后取消获取。
执行流程如下:
unlock()
作用为释放锁,所以必须与lock(),tryLock(),lockInterruptibly()成对使用,即加锁就必须释放锁。
进入方法,可知调用的是sync的release(1)方法,其核心是调用AQS的tryRelease(1)和unparkSuccessor()方法,调用流程如下:
sync的release()方法源代码如下:
public final boolean release(int arg) {
//尝试释放锁
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
//唤醒等待队列中的线程获得锁
unparkSuccessor(h);
return true;
}
return false;
}
AQS中的tryRelease()方法为abstract方法,实际调用为sync中重写的tryRelease()方法,源代码如下:
protected final boolean tryRelease(int releases) {
//此处若不为0,则表示当前线程发生重入锁,任然保留锁
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;
}
通过源码可分析得执行流程为:
newCondition()
首先先看下是如何使用
public class T02_Lock {
Condition c1=rLock.newCondition();
Condition c2=rLock.newCondition();
boolean flag=false;
void testCondition(){
new Thread(()->{
rLock.lock();
try {
while (!flag){
System.out.println(Thread.currentThread().getName()+":我艹,搞不定啊");
c2.signal();
c1.await();
}
System.out.println(Thread.currentThread().getName()+":劳资又回来了");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
rLock.unlock();
}
},"thread-1").start();
new Thread(()->{
rLock.lock();
try{
flag=true;
System.out.println(Thread.currentThread().getName()+":去吧,皮卡丘");
c1.signal();
c2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"thread-2").start();
}
public static void main(String[] args) {
T02_Lock t=new T02_Lock();
t.testCondition();
}
}
输出结果:
当中的Condition直译过来是“状况”,可以理解为:“一系列相同的状况”。
通过查看源码可知,调用newCondition()方法,实际是调用sync的newCondition()方法,并创建一个ConditionObject对象,源代码如下:
final ConditionObject newCondition() {
return new ConditionObject();
}
查看ConditionObject实现,它是AQS中的一个内部类,并且是Condition接口的一个实现。里面包含了Node链表,也就是说一个Condition维护了一个等待队列。执行asingal()和await()方法时,能够指定哪个等待队列被唤醒或者等待。
总结
lock()和tryLock()的区别:lock()请求获取锁,若失败,会进入等待队列,直到获取成功;tryLock()请求获取锁,若失败,直接返回false。这也导致使用上有些许不一样
void test2(){
System.out.println("thread2 begin ....");
rLock.lock();
System.out.println("thread2 getLock ....");
rLock.unlock();
System.out.println("thread2 end ....");
}
void test3(){
boolean locked=rLock.tryLock();
try
{
i++;
}finally {
if(locked)rLock.unlock();
}
}
读写锁----ReetrantReadWriteLock
概念
是什么?
ReetrantReadWriteLock是ReadWriteLock的一个实现类,
ReadWriteLock是什么?
ReadWriteLock是java.util.concurrent.lock包中的一个接口,其中包含了一对方法,分别返回一个锁,一个读锁(read),一个写锁(write),两个锁相互关联。
性质:
- 只读锁,可以多个“读”线程共享;
- 写锁,只能一个“写”线程独占;
- 当write被锁时,或者此时仍有线程等待write,read不能被获取(当数据被“写/修改”时,不允许读取);
- 当read,write未被锁时,write才能被获取(当数据当前即没有被读取,也没有被修改时,才允许修改);
- write锁可以降级为read锁(修改完以后可以立马读取);
意义:
在reetractLock中,每次只允许一个线程对共享数据独占访问。而当大多线程只是对数据进行读取,而不进行修改时,则会降低效率,故设计出读写锁,运行多个“只读”线程同时访问共享数据。因此应用场景大多为对数据“读”的概率要远大于“写”的概率,这样能大大提高程序的并发量。
使用示例
public class T03_ReadWriteLock {
String data;
volatile boolean cacheValid;
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
void processCachedData() {
rwl.readLock().lock();
if (!cacheValid) {
// Must release read lock before acquiring write lock
rwl.readLock().unlock();
rwl.writeLock().lock();
try {
// Recheck state because another thread might have
// acquired write lock and changed state before we did.
if (!cacheValid) {
data = "Hello ";
cacheValid = true;
}
// Downgrade by acquiring read lock before releasing write lock
rwl.readLock().lock();
} finally {
rwl.writeLock().unlock(); // Unlock write, still hold read
}
}
try {
System.out.println(data);
} finally {
rwl.readLock().unlock();
}
}
public static void main(String[] args) {
T03_ReadWriteLock t=new T03_ReadWriteLock();
t.processCachedData();
}
在上述代码中,先获取读锁 rwl.readLock().lock(),在而后对缓存数据进行操作,在获取写锁之前,必须将读锁释放,否则写锁无法获取。期间当前线程未释放写锁,就开始请求读锁,此时是锁降级,最后分别将写锁,读锁关闭。
锁降级过程
源码分析
分析源码,相关类依赖如下:
ReetranReadWriteLock主要依赖于AQS,Sync及其具体实现。在源码中我们可以看到,整个ReetranReadWriteLock的锁状态是由AQS中的state维护,它是一个32位的int,而在读写锁中这一个int需要维护两个锁的不同状态。故将state分为两部分,通过两部分的值分别保存两个锁的状态。分析源码可知,它是将一个32位的int数字划分为两个16位的short,前16位保存持有read锁的线程数(sharedCount),后16位保存写锁的线程重入数(exclusiveCount)。当两个为0时,表示当前锁空闲。当其中一个大于0,表示其对应的锁已被占用。
源代码如下:
/*
* Read vs write count extraction constants and functions.
* Lock state is logically divided into two unsigned shorts:
* The lower one representing the exclusive (writer) lock hold count,
* and the upper the shared (reader) hold count.
*/
static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
//最大共享线程量
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
/** Returns the number of shared holds represented in count
取state的前16位 */
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
/** Returns the number of exclusive holds represented in count
取state的后16位 */
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
Sync中有两个抽象方法,由其继承类具体实现,两个方法为:
abstract boolean readerShouldBlock();
abstract boolean writerShouldBlock();
其功能为:当有别的线程也在尝试获取锁时,是否应该阻塞。
这个依赖于具体的实现机制,即当前读写锁是公平锁,还是非公平锁。其具体实现如下:
公平锁:方法表示前面是否有等待线程,那么为了遵循公平,当前线程也就应该被挂起。
final boolean writerShouldBlock() { return hasQueuedPredecessors();}
final boolean readerShouldBlock() { return hasQueuedPredecessors();}
依赖的是AQS中的hasQueuedPredecessors()方法,具体实现如下
public final boolean hasQueuedPredecessors() {
// 当前等待队列中是否有等待的线程
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
非公平锁:
先来看具体实现的源码:
final boolean writerShouldBlock() {
return false; // writers can always barge
}
final boolean readerShouldBlock() {
/* As a heuristic to avoid indefinite writer starvation,
* block if the thread that momentarily appears to be head
* of queue, if one exists, is a waiting writer. This is
* only a probabilistic effect since a new reader will not
* block if there is a waiting writer behind other enabled
* readers that have not yet drained from the queue.
*/
//查看当前等待队列中的第一个线程是不是独占线程(写线程),若是,则返回true
return apparentlyFirstQueuedIsExclusive();
}
从源码中可以看到,在writerShouldBlock()方法中,直接返回false,表示不需要阻塞;在readerShouldBlock()中,以来的是AQS的apparentlyFirstQueuedIsExclusive()方法,看一下具体实现:
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h = head) != null &&
(s = h.next) != null &&
!s.isShared() &&
s.thread != null;
从源码可知,判断当前等待队列中头结点的下一个节点线程是否是独占线程,若是,返回true。
由上述可总结读写锁阻塞机制如下:
公平锁:不管是读锁,还是写锁,都先排队(前面有线程等待才排队)。(保证公平机制)
非公平锁:写锁:直接先尝试获取,无需等待,若获取失败,在进入队列中等待。
读锁:先判断当前等待队列中第一个等待的是不是请求写锁 的,若是,则进入队列等待,若不是,则尝试获取。 (为了防止写锁长时间不被获取,写线程得不到执行的机会)
常用方法
读锁获取-----readLock.lock()
public void lock() {
sync.acquireShared(1);
}
public final void acquireShared(int arg) {
//尝试获取锁,返回0表示失败
if (tryAcquireShared(arg) < 0)
//进入等待队列
doAcquireShared(arg);
}
通过源码,我们可以得知,lock()方法逻辑为:首先调用tryAcquireShared()方法,尝试获取锁,若失败,调用doAcquireShared()进入等待队列,两个方法源代码如下:
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
//若当前有独占线程(写锁是否被占用),且当前非重入,则直接返回失败
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
//获取当前读锁的共享数量
int r = sharedCount(c);
//判断当前是否应阻塞(与具体锁(公平/非公平)有关)
if (!readerShouldBlock() &&
//当前共享数量小于最大共享数
r < MAX_COUNT &&
//更新AQS中state状态成功
compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {
//当前线程为第一个线程,锁状态为free
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
//当前线程为第一个线程的重入
firstReaderHoldCount++;
} else {
//给当前线程设置HoldCounter,重入计数更新
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
//若失败,则放入循环
return fullTryAcquireShared(current);
}
分析源码可知Sync尝试获取读锁机制如下:
- 若此时写锁被锁被占有,且独占线程非当前线程,则获取失败;
- 写锁空闲,则由 公平机制 判断当前读线程是否应该阻塞,若不阻塞,则当前读锁共享数不得大于最大共享数,AQS更新锁状态成功,此时可获得读锁;
- 其他情况下,均进入循环体fullTryAcquireShared(),等待获取。
fullTryAcquireShared()源代码如下:
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
for (;;) {
int c = getState();
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
//若当前线程已经占有写锁,应当给予通过,不应阻塞,否则会造成死锁
} else if (readerShouldBlock()) {
// Make sure we're not acquiring read lock reentrantly
if (firstReader == current) {
} else {
if (rh == null) {
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current)) {
rh = readHolds.get();
if (rh.count == 0)
readHolds.remove();
}
}
if (rh.count == 0)
return -1;
}
}
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
if (compareAndSetState(c, c + SHARED_UNIT)) {
if (sharedCount(c) == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
if (rh == null)
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
cachedHoldCounter = rh; // cache for release
}
return 1;
}
}
}
阅读上面源码可以发现,代码和 doAcquireShared()方法有很相似,循环的判断当前是否满足条件,直到返回成功或失败。当获取锁失败时,调用doAcquireShared()方法,源代码如下:
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);//加入等待队列
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();//获取前继节点
if (p == head) {//当前继节点为头结点,此时当前线程位于等待队列的第一个,有机会获取锁
int r = tryAcquireShared(arg);//再次尝试获取锁
if (r >= 0) {//获取锁成功
setHeadAndPropagate(node, r);//重新设置等待队列节点,并唤醒后面等待获取readlock的线程
p.next = null; // help GC
if (interrupted)//等待过程中被中断
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;//被中断
}
} finally {
if (failed)
cancelAcquire(node);
}
}
分析源码可知,执行流程是在一个循环体内死循环,不断尝试获取锁,直到成功获取或被中断。
读锁获取流程图总结图如下:
读锁 释放----readLock.unlock()
首先查看源代码如下:
public void unlock() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {//释放锁
doReleaseShared();//成功则唤醒队列等待线程
return true;
}
return false;
}
可知,调用的是tryReleaseShared(1),对源码进行分析:
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
//清理firstReader中缓存
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else {
//清理HoldCounter中的重入计数
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
int count = rh.count;
if (count <= 1) {
readHolds.remove();
if (count <= 0)
throw unmatchedUnlockException();
}
--rh.count;
}
for (;;) {
// 更新state值
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
tryReleaseShared()方法最核心部分为for中的死循环,本质是更新state状态,将共享数量减1;而上半部分的功能为将缓存以及其记录信息删除或者更新。
写锁 获取----writeLock.lock()
public void lock() {
sync.acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
由源代码可知,writeLock.lock()方法与上述reetranctLock的lock()方法的步骤一样,都是调用Sync的acquire()方法。但是这是个抽象方法,由具体子类实现。代码如下:
protected final boolean tryAcquire(int acquires) {
/*
* Walkthrough:
* 1. If read count nonzero or write count nonzero
* and owner is a different thread, fail.
* 2. If count would saturate, fail. (This can only
* happen if count is already nonzero.)
* 3. Otherwise, this thread is eligible for lock if
* it is either a reentrant acquire or
* queue policy allows it. If so, update state
* and set owner.
*/
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);
//当前锁为非空闲状态
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 0)
//此时读锁被获取,写锁空闲||当前独占锁线程非当前线程,返回失败
if (w == 0 || current != getExclusiveOwnerThread())
return false;
//写锁线程数超过最大独占线程数 ,写锁获取失败,抛出异常
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
//更新锁状态
setState(c + acquires);
return true;
}
//根据公平机制,当前是否需要阻塞
if (writerShouldBlock() ||
//获取锁,更新锁状态
!compareAndSetState(c, c + acquires))
return false;
//将当前线程设为独占线程
setExclusiveOwnerThread(current);
return true;
}
执行过程:若当前读锁被占用,或当前独占线程非当前线程,获取失败; 写锁线程超过最大线程 获取失败;根据公平机制,当前线程需要阻塞等待,获取失败;更新state状态失败,获取失败,否则获取成功,并将当前线程置为独占线程。
写锁 释放-----writeLock.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;
}
与reentractLock.unlock()方法一样,都是调用Sync.release()方法,方法逻辑与reentractLock.unlock()一样,但是tryRelease()方法由具体子类实现。源代码如下:
protected final boolean tryRelease(int releases) {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
int nextc = getState() - releases;
boolean free = exclusiveCount(nextc) == 0;//更新独占线程数
if (free)
setExclusiveOwnerThread(null);//将当前独占线程置为null
setState(nextc);//更新state状态
return free;
}
总结
公平锁和非公平锁: 公平锁先排队,在获取“锁”;非公平锁只有当前获取读锁,且第一个等待的线程是获取“写”锁,才排队,情 况,都先尝试获取,获取失败才排队; 读锁获取:在等待队列中成功获取到读锁后,会依次唤醒后面与他一起共享当前读锁的线程。