1. Lock API
1. Locks 包类层次结构
2. Lock 接口
方法签名 | 描述 |
---|---|
void lock(); | 获取锁(不死不休) |
boolean tryLock(); | 获取锁(浅尝辄止) |
boolean tryLock(long time, TimeUnit unit) throws InterruptedException; | 获取锁(过时不候) |
void lockInterruptibly() throws InterruptedException; | 获取锁(任人摆布) |
void unlock(); | 释放锁 |
Condition newCondition(); |
-
结论:
- lock() 最常用,并且线程不会被中断;
- lockInterruptibly() 方法一般更昂贵,有的 impl 可能没有实现 lockInterruptibly(),只有真的需要效应中断时,才使用,使用之前看看 impl 对该方法的描述。
public class Demo1_GetLock {
// 公平锁
// static Lock lock = new ReentrantLock(true);
// 非公平锁
static Lock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
// 主线程 拿到锁
lock.lock();
Thread th = new Thread(() -> {
/*
// 子线程 获取锁(浅尝辄止)
boolean result = lock.tryLock();
System.out.println("是否获取到锁: " + result);
*/
/*
// 子线程 获取锁(过时不候)
try {
System.out.println("开始获取锁");
boolean result = lock.tryLock(5, TimeUnit.SECONDS);
System.out.println("是否获取到锁: " + result);
} catch (InterruptedException e) {
e.printStackTrace();
}
*/
/*
// 子线程 获取锁(任人摆布)
try {
System.out.println("开始获取锁 Interruptibly");
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("父线程让我停止了。");
}
*/
// 子线程 获取锁(不死不休)
System.out.println("开始获取锁");
lock.lock();
System.out.println("成功获取到锁");
});
th.start();
Thread.sleep(7000);
th.interrupt();
System.out.println("th interrupted ...");
Thread.sleep(15000);
lock.unlock();
}
}
3. Condition
- Object 中的 wait()、notify()、notifyAll() 只能和 synchronized 配合使用,可以唤醒一个或者全部(单个等待集);
- Condition 需要与 Lock 配合使用,提供多个等待集合,更精确地控制。
协作方式 | 死锁方式1 | 死锁方式2(先唤醒,再挂起) | 备注 |
---|---|---|---|
suspend/resume | 死锁 | 死锁 | 弃用 |
wait/notify | 不死锁 | 死锁 | 只用于 synchronized 关键字 |
park/unpark | 死锁 | 不死锁 | |
condition | 不死锁 | 死锁 |
-
模拟 condition 死锁
public class Demo3_Condition { private static Lock lock = new ReentrantLock(); private static Condition condition = lock.newCondition(); public static void main(String[] args) throws InterruptedException { Thread th = new Thread(() -> { try { // 模拟先调用 signal,再调用 await,会进入死锁状态 Thread.sleep(6000); } catch (InterruptedException e) { e.printStackTrace(); } lock.lock(); try { System.out.println("here I am ... 1"); condition.await(); System.out.println("here I am ... 2"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } }); th.start(); Thread.sleep(5000); lock.lock(); condition.signal(); lock.unlock(); } }
-
经典示例: 通过 condition 实现阻塞队列
public class Demo4_Condition3 { public static void main(String[] args) throws InterruptedException { MyBlockingQueue bb = new MyBlockingQueue(5); new Thread(() -> { for (int i = 0; i < 20; i ++) { bb.put("x" + i); } }).start(); Thread.sleep(3000); System.out.println("开始从队列中取元素..."); for (int i = 0; i < 10; i ++) { bb.take(); Thread.sleep(3000); } } } /** * 自己实现一个阻塞队列,只能存储 n 个元素 * put 时,若队列未满,直接 put * 若队列已满,就阻塞,直到再有空间 * get 时,若队列中有元素,获取到元素 * 若无元素,则等待元素 */ class MyBlockingQueue { List<Object> list = new ArrayList<>(); private int length; private Lock lock = new ReentrantLock(); private Condition putCondition = lock.newCondition(); private Condition takeCondition = lock.newCondition(); public MyBlockingQueue(int length) { this.length = length; } public void put(Object obj) { lock.lock(); try { while (true) { if (list.size() < this.length) { list.add(obj); takeCondition.signal(); System.out.println("put: " + obj); return; } else { putCondition.await(); } } } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public Object take() { lock.lock(); try { while (true) { if (list.size() > 0) { Object obj = list.remove(0); putCondition.signal(); System.out.println("tack: " + obj); return obj; } else { takeCondition.await(); } } } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); return null; } } }
4. ReentrantLock
public class Demo5_Reentrant {
static Lock lock = new ReentrantLock(); // 可重入锁
public static void main(String[] args) {
System.out.println("get lock 1 ...");
lock.lock();
System.out.println("get lock 2 ...");
lock.lock();
// 打印消息,说明再次拿到锁
System.out.println("here I am ...");
System.out.println("unlock 1 ...");
lock.unlock();
System.out.println("unlock 2 ...");
lock.unlock();
System.out.println("unlock 3 ...");
lock.unlock(); // 第三次释放锁,报错
}
}
- 自己实现一个 ReentrantLock
public class MyReentrantLock implements Lock {
// 锁拥有者
AtomicReference<Thread> owner = new AtomicReference<>();
// 记录冲入次数的 count
AtomicInteger count = new AtomicInteger(0);
// 等待队列
private LinkedBlockingDeque<Thread> waiters = new LinkedBlockingDeque<>();
@Override
public boolean tryLock() {
int ct = count.get();
// 判断 count 是否为 0,若 count!=0,说明锁被占用
if (ct != 0) {
if (Thread.currentThread() == owner.get()) {
// 判断是否是当前线程占用,如果是当前线程占用,做重入
count.set(ct + 1);
return true;
}
} else {
// 若 count=0,说明锁未被占用,通过 CAS 来抢锁
if (count.compareAndSet(ct, ct + 1)) {
owner.set(Thread.currentThread());
return true;
}
}
return false;
}
@Override
public void lock() {
if (! tryLock()) {
// 没有拿到锁,加入等待队列
waiters.offer(Thread.currentThread());
LockSupport.park();
while (true) {
// 若线程是队列头部,尝试加锁
Thread head = waiters.peek();
if (head == Thread.currentThread()) {
if (!tryLock()) {
LockSupport.park();
} else {
waiters.poll();
return;
}
} else {
// 线程挂起
LockSupport.park();
}
}
}
}
public boolean tryUnlock() {
if (owner.get() != Thread.currentThread()) {
throw new IllegalMonitorStateException();
} else {
// unlock 是将 count-1
int ct = count.get();
int nextC = ct - 1;
count.set(nextC);
// 判断 count 是否为 0,如果为 0,就释放锁成功,否则释放失败
if (nextC == 0) {
owner.compareAndSet(Thread.currentThread(), null);
return true;
} else {
return false;
}
}
}
@Override
public void unlock() {
if (tryUnlock()) {
Thread head = waiters.peek();
if (head != null) {
LockSupport.unpark(head);
}
}
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public Condition newCondition() {
return null;
}
}
5. synchronized vs Lock
-
Synchronized
- 优点
- 使用简单,语义清晰,哪里需要点哪里。
- 由 JVM 提供,提供来多种优化方案(锁粗化、锁消除、偏向锁、轻量级锁)。
- 锁的释放由虚拟机来完成,不用人工干预,也降低了死锁的可能性。
- 缺点
- 无法实现一些锁的高级功能如: 公平锁、中断锁、超时锁、读写锁、共享锁等。
- 优点
-
Lock
- 优点
- 所有 synchronized 的缺点。
- 可以实现更过的功能,让 synchronized 缺点更多。
- 缺点
- 需手动释放锁 unlock,新手使用不当可能造成死锁。
- 优点
2. 读写锁: ReadWriteLock
1. 概念
- 维护一对关联锁,一个只用于读操作,一个只用于写操作。
- 读锁可以由多个线程同时持有,写锁是排他的,同一时间,两把锁不能被不同线程持有。
2. Lock 在读多写少的情况下的缺点演示
public class Demo6_ReadWrite01 {
volatile long i = 0;
Lock lock = new ReentrantLock();
public void read() {
lock.lock();
long a = i;
lock.unlock();
}
public void write() {
lock.lock();
i ++;
lock.unlock();
}
public static void main(String[] args) {
final Demo6_ReadWrite01 demo = new Demo6_ReadWrite01();
for (int i = 0; i <= 30; i ++) {
int n = i;
new Thread(() -> {
long startTime = System.currentTimeMillis();
for (int j = 0; j < 400000; j ++) {
if (n == 0) demo.write();
else demo.read();
}
}).start();
}
long endTime = System.currentTimeMillis();
}
}
-
此时,只有第一次的时候是写操作,其他都是读操作。使用 Lock,每次只能有一个线程做读取操作,效率会非常低。
-
如下是 ReadWriteLock 的演示
public class Demo6_ReadWrite02 {
volatile long i = 0;
ReadWriteLock rwLock = new ReentrantReadWriteLock();
public void read() {
rwLock.readLock().lock();
long a = i;
rwLock.readLock().unlock();
}
public void write() {
rwLock.writeLock().lock();
i ++;
rwLock.writeLock().unlock();
}
public static void main(String[] args) {
final Demo6_ReadWrite02 demo = new Demo6_ReadWrite02();
for (int i = 0; i <= 30; i ++) {
int n = i;
new Thread(() -> {
long startTime = System.currentTimeMillis();
for (int j = 0; j < 400000; j ++) {
if (n == 0) demo.write();
else demo.read();
}
}).start();
}
long endTime = System.currentTimeMillis();
}
}
3. 适用场景
-
适合读取操作多于写入操作的场景,改进互斥锁的性能。比如: 集合的并发线程安全性改造、缓存组件。
-
将 HashMap 改造成线程安全的
// 将 hashmap 改造成一个并发安全的
// 这是 ReentrantReadWriteLock 注释中给出的一个示例
public class Demo7_Map {
private final Map<String, Object> m = new HashMap<>();
private final ReadWriteLock rwl = new ReentrantReadWriteLock();
private final Lock r = rwl.readLock();
private final Lock w = rwl.writeLock();
public Object get(String key) {
r.lock();
try {
return m.get(key);
} finally {
r.unlock();
}
}
public Object put(String key, Object value) {
w.lock();
try {
return m.put(key, value);
} finally {
w.unlock();
}
}
public void clear() {
w.lock();
try {
m.clear();
} finally {
w.unlock();
}
}
}
4. 锁降级
- 指的是写锁降级为读锁。持有写锁的同时,再获取读锁,随后释放写锁的过程。
- 写锁是线程独占,读锁是共享,所以写->读是降级。(读->写是不能实现的)
public class Demo8_CacheData {
public static void main(String[] args) {
}
}
class TeacherInfoCache {
static volatile boolean cacheValid;
static final ReadWriteLock rwl = new ReentrantReadWriteLock();
/**
* 实现一个使用 Cache 数据的方法
* 如果缓存可用,就直接获取数据
* 如果缓存不可用,从 DB 加载数据到缓存
* 拿到数据之后,需要使用数据,这期间,数据不能发生变化
*/
static void processCacheData(String dataKey) {
Object data = null;
rwl.readLock().lock();
try {
if (cacheValid) {
data = Redis.data.get(dataKey);
} else {
// 到数据库查询数据
// DataBase.queryUserInfo(); // 存在缓存雪崩的问题
rwl.readLock().lock();
rwl.writeLock().lock();
try {
if (! cacheValid) {
data = DataBase.queryUserInfo();
Redis.data.put(dataKey, data);
cacheValid = true;
} else {
data = Redis.data.get(dataKey);
}
} finally {
// 锁降级 线程在占有写锁的时候,获取读锁,同时拥有读写锁
rwl.readLock().lock();
rwl.writeLock().unlock();
}
}
} finally {
rwl.readLock().unlock();
}
}
}
class DataBase {
static String queryUserInfo() {
System.out.println("查询数据库...");
return "name:Kody,age:40,gender:true";
}
}
class Redis {
static Map<String, Object> data = new HashMap<>();
}
5. 读写锁的内部原理
3. 读写锁实现
1. 自己实现一个重入锁和读写锁
- 公共代码
public class MyAQS {
AtomicInteger readCount = new AtomicInteger(0);
AtomicInteger writeCount = new AtomicInteger(0);
// 独占锁,拥有者
AtomicReference<Thread> owner = new AtomicReference<>();
// 等待队列
public volatile LinkedBlockingQueue<WaitNode> waiters = new LinkedBlockingQueue<>();
public class WaitNode {
int type = 0; // 0 为想获取独占锁的线程,1 为想获取共享所的线程
Thread thread = null;
int arg = 0;
public WaitNode(Thread thread, int type, int age) {
this.thread = thread;
this.type = type;
this.arg = arg;
}
}
public void lock() {
int arg = 1;
// 尝试获取独占锁,若成功,退出方法,若失败...
if (! tryLock(arg)) {
// 标记为独占所
WaitNode waitNode = new WaitNode(Thread.currentThread(), 0, arg);
waiters.offer(waitNode); // 进入等待队列
// 循环尝试拿锁
while (true) {
// 若队列头部是当前线程
WaitNode head = waiters.peek();
if (head != null && head.thread == Thread.currentThread()) {
if (! tryLock(arg)) { // 再次尝试获取独占锁
LockSupport.park(); // 若失败,挂起线程
} else { // 若成功获取
waiters.poll(); // 将当前线程从队列头部移除
return; // 并退出方法
}
} else { // 若不是队列头部元素
LockSupport.park(); // 将当前线程挂起
}
}
}
}
// 释放独占锁
public boolean unlock() {
int arg = 1;
// 尝试释放独占锁,若成功返回true,若失败...
if (tryUnlock(arg)) {
WaitNode head = waiters.peek();
if (head != null) {
Thread th = head.thread;
LockSupport.unpark(th); // 唤醒队列头部的线程
}
return true; // 返回 true
}
return false;
}
// 获取共享锁
public void lockShared() {
int arg = 1;
if (tryLockShared(arg) < 0) { // 如果 tryLockShared 失败
// 将当前线程放入队列
WaitNode node = new WaitNode(Thread.currentThread(), 1, arg);
waiters.offer(node); // 加入队列
while (true) {
// 若队列头部的元素是当前线程
WaitNode head = waiters.peek();
if (head != null && head.thread == Thread.currentThread()) {
if (tryLockShared(arg) >= 0) { // 尝试获取共享锁,成功
waiters.poll(); // 将当前线程从队列中移除
WaitNode next = waiters.peek();
if (next != null && next.type == 1) { // 如果下一个线程也是等待共享锁
LockSupport.unpark(next.thread); // 将其唤醒
}
return; // 退出方法
}
} else { // 若尝试失败
LockSupport.park();
}
}
}
}
// 解锁共享锁
public boolean unLockShared() {
int arg = 1;
if (tryUnLockShared(arg)) { // 当 read count 变为 0,才叫 release share 成功
WaitNode next = waiters.peek();
if (next != null) {
LockSupport.unpark(next.thread);
}
return true;
}
return false;
}
// 尝试获取独占锁
public boolean tryLock(int acquires) {
throw new UnsupportedOperationException();
}
// 尝试释放独占锁
public boolean tryUnlock(int releases) {
throw new UnsupportedOperationException();
}
// 尝试获取共享锁
public int tryLockShared(int acquires) {
throw new UnsupportedOperationException();
}
// 尝试解锁共享锁
public boolean tryUnLockShared(int releases) {
throw new UnsupportedOperationException();
}
}
- 重入锁实现
public class MyReentrantLock implements Lock {
private boolean isFair;
public MyReentrantLock(boolean isFair) {
this.isFair = isFair;
}
MyAQS common = new MyAQS() {
public boolean tryLock(int acquires) {
if (isFair) {
return tryFairLock(acquires);
} else {
return tryNotFairLock(acquires);
}
}
// 非公平锁
public boolean tryNotFairLock(int acquires) {
// 如果 read count != 0,返回 false
if (readCount.get() != 0) {
return false;
}
int wct = writeCount.get(); // 拿到 独占锁 当前状态
if (wct == 0) {
if (writeCount.compareAndSet(wct, wct + 1)) { // 通过修改 state 来抢锁
owner.set(Thread.currentThread()); // 抢到锁后,直接修改 owner 为当前线程
return true;
}
} else if (owner.get() == Thread.currentThread()) {
writeCount.set(wct + acquires); // 修改 count 的值
return true;
}
return false;
}
// 公平锁
public boolean tryFairLock(int acquires) {
// 如果 read count != 0,返回 false
if (readCount.get() != 0) {
return false;
}
int wct = writeCount.get(); // 拿到 独占锁 当前状态
if (wct == 0) {
WaitNode head = waiters.peek();
if (head != null && head.thread == Thread.currentThread() &&
writeCount.compareAndSet(wct, wct + 1)) { // 通过修改 state 来抢锁
owner.set(Thread.currentThread()); // 抢到锁后,直接修改 owner 为当前线程
return true;
}
} else if (owner.get() == Thread.currentThread()) {
writeCount.set(wct + acquires); // 修改 count 的值
return true;
}
return false;
}
// 尝试释放独占锁
public boolean tryUnlock(int releases) {
// 若当前线程没有持有独占锁
if (owner.get() != Thread.currentThread()) {
throw new IllegalMonitorStateException();
}
int wc = writeCount.get();
int nextc = wc - releases; // 计算 独占锁剩余占用
writeCount.set(nextc);
if (nextc == 0) { // 是否完全释放
owner.compareAndSet(Thread.currentThread(), null);
return true;
} else {
return false;
}
}
};
public void lock() {
common.lock();
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
public boolean tryLock() {
return common.tryLock(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public void unlock() {
common.unlock();
}
@Override
public Condition newCondition() {
return null;
}
}
- 读写锁实现
public class MyReadWriteLock implements ReadWriteLock {
MyAQS common = new MyAQS() {
// 尝试获取独占锁
public boolean tryLock(int acquires) {
// 如果 read count != 0,返回 false
if (readCount.get() != 0) {
return false;
}
int wct = writeCount.get(); // 拿到 独占锁 当前状态
if (wct == 0) {
if (writeCount.compareAndSet(wct, wct + 1)) { // 通过修改 state 来抢锁
owner.set(Thread.currentThread()); // 抢到锁后,直接修改 owner 为当前线程
return true;
}
} else if (owner.get() == Thread.currentThread()) {
writeCount.set(wct + acquires); // 修改 count 的值
return true;
}
return false;
}
// 尝试释放独占锁
public boolean tryUnlock(int releases) {
// 若当前线程没有持有独占锁
if (owner.get() != Thread.currentThread()) {
throw new IllegalMonitorStateException();
}
int wc = writeCount.get();
int nextc = wc - releases; // 计算 独占锁剩余占用
writeCount.set(nextc);
if (nextc == 0) { // 是否完全释放
owner.compareAndSet(Thread.currentThread(), null);
return true;
} else {
return false;
}
}
// 尝试获取共享锁
public int tryLockShared(int acquires) {
while (true) {
if (writeCount.get() != 0 && owner.get() != Thread.currentThread()) {
return -1;
}
int rct = readCount.get();
if (readCount.compareAndSet(rct, rct + acquires)) {
return 1;
}
}
}
// 尝试解锁共享锁
public boolean tryUnLockShared(int releases) {
while (true) {
int rc = readCount.get();
int nextc = rc - releases;
if (readCount.compareAndSet(rc, nextc)) {
return nextc == 0;
}
}
}
};
@Override
public Lock readLock() {
return new Lock() {
@Override
public void lock() {
common.lock();
}
@Override
public boolean tryLock() {
return common.tryLock(1);
}
@Override
public void unlock() {
common.unlock();
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public Condition newCondition() {
return null;
}
};
}
@Override
public Lock writeLock() {
return new Lock() {
@Override
public void lock() {
common.lockShared();
}
@Override
public boolean tryLock() {
return common.tryLockShared(1) == 1;
}
@Override
public void unlock() {
common.unLockShared();
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public Condition newCondition() {
return null;
}
};
}
}
- 重入锁测试
public class Test_ReentrantLock {
volatile static int i = 0;
static MyReentrantLock lc = new MyReentrantLock(true);
public static void add() {
lc.lock();
i ++;
lc.unlock();
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i ++) {
new Thread(() -> {
for (int j = 0; j < 10000; j ++) {
add();
}
System.out.println("done...");
}).start();
}
Thread.sleep(6000);
System.out.println(i);
}
}
- 读写锁测试
public class Test_ReadWriteLock {
static MyReadWriteLock rwLock = new MyReadWriteLock();
static volatile int i = 0;
static void add() {
i ++;
}
public static void main(String[] args) throws InterruptedException {
long startTime = System.currentTimeMillis();
for (int a = 0; a < 20000; a ++) {
final int n = a;
new Thread(() -> {
if (n % 5 == 0) {
rwLock.writeLock().lock();
add();
rwLock.writeLock().unlock();
} else {
rwLock.readLock().lock();
System.out.println("i = " + i);
int b = i;
rwLock.readLock().unlock();
}
}).start();
}
while (true) {
System.out.println("目前耗时: " + (System.currentTimeMillis() - startTime));
Thread.sleep(1000);
System.out.println("i = " + i);
}
}
}
2. AQS(AbstractQueuedSynchronizer) 抽象队列同步器
- java.util.concurrent.locks.AbstractQueuedSynchronizer
- 提供了对资源占用、释放,线程的挂起、唤醒的逻辑。
- 预留来各种 try 方法给用户实现。
- 可以用在各种需要控制资源争用的场景中。(ReentrantLock/CountDownLatch/Semphore)
3. ReadWriteLock 用一个 int 存储了两个 count 值
来源:oschina
链接:https://my.oschina.net/shadowolf/blog/3220058