ReentrantLock
1. 特点
- 可打断,可重入
- 可以设置超时时间
- 可以设置为公平锁
- 支持多个条件变量
- 支持读写锁
- 基本语法
reentrantLock.lock();
try {
//业务代码
} finally {
reentrantLock.unlock();
}
2. 重入
package org.example;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j
public class ReEntryLock {
static ReentrantLock lock = new ReentrantLock();
public static void lock1() {
lock.lock();
try {
log.info("执行lock1");
} finally {
lock.unlock();
}
}
public static void lock2() {
lock.lock();
try {
log.info("执行lock2");
//重入
lock1();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
lock2();
}
}
- 查看打印
3. 可打断
t—线程
lock.lockInterruptibly();
标识可以打断
怎么打断
t.interrupt();
package org.example;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j
public class InterruptLock {
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
//t1先获取锁,然后睡眠5s
new Thread(() -> {
try {
lock.lock();
log.info("t1获取锁");
TimeUnit.SECONDS.sleep(5);
log.info("t1睡眠了5s");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "t1").start();
TimeUnit.SECONDS.sleep(1);
//t2加锁失败,因为被t1持有了
Thread t2 = new Thread(() -> {
try {
lock.lockInterruptibly();
log.info("t2获取锁了,开始执行代码");
} catch (InterruptedException e) {
log.info("t2被打断了,没有获取到锁");
} finally {
lock.unlock();
}
}, "t2");
t2.start();
//由于t2可以被打断,所以1s后打断t2,不在等待t1释放锁
log.info("主线程睡眠1s后打断t2");
TimeUnit.SECONDS.sleep(2);
t2.interrupt();
}
}
- 查看打印
4. 超时
lock.tryLock(2, TimeUnit.SECONDS)
package org.example;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j
public class OutTimeLock {
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
Thread t1=new Thread(()->{
log.info("t1启动");
try {
//尝试获取锁,如果失败就返回
if (!lock.tryLock(2, TimeUnit.SECONDS)) {
log.info("t1获取锁失败");
return;
}
try {
log.info("t1获得锁");
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1");
try {
lock.lock();
log.info("主线程获得了锁");
t1.start();
TimeUnit.SECONDS.sleep(3);
} finally {
lock.unlock();
}
}
}
- 查看打印
5. 多条件
- synchronized 中就有条件变量,就是waitSet,不满足条件的线程进入waitSet;而Lock也有waitSet而且有多个
创建条件
Condition waitA = lock.newCondition()
阻塞
waitA.await();
唤醒
waitA.signal();
package org.example;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j
public class MultiConditionLock {
static ReentrantLock lock = new ReentrantLock();
static boolean aFlag = false;
static boolean bFlag = false;
static Condition waitA = lock.newCondition();
static Condition waitB = lock.newCondition();
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
lock.lock();
try {
log.info("aFlag="+aFlag);
while (!aFlag) {
log.info("aFlag="+aFlag);
try {
waitA.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("aFlag="+aFlag);
} finally {
lock.unlock();
}
},"a").start();
new Thread(()->{
lock.lock();
try {
log.info("bFlag="+bFlag);
while (!bFlag) {
log.info("bFlag="+bFlag);
try {
waitB.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.info("bFlag="+bFlag);
} finally {
lock.unlock();
}
},"b").start();
Thread.sleep(1000);
new Thread(()->{
lock.lock();
try {
aFlag = true;
log.info("修改aFlag = true;");
waitA.signal();
bFlag = true;
log.info("修改bFlag = true;");
waitB.signal();
} finally {
lock.unlock();
}
}).start();
}
}
- 查看打印
6. 读写锁
- 读写互斥
- 写写互斥
- 读读并发
读写锁读锁不支持条件,ReentrantReadWriteLock的 newCondition 会直接exception
6.1 读写互斥
package org.example;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@Slf4j
public class ReadWriteLock {
static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
static Lock r = rwl.readLock();
static Lock w = rwl.writeLock();
public static void m(String str) {
log.info(str);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
//读
new Thread(()->{
log.debug("读锁");
r.lock();
try {
for (int i=0; i < 5; i++) {
m("读锁" + i);
}
} finally {
r.unlock();
}
},"t1").start();
//写
new Thread(()->{
log.debug("写锁");
w.lock();
try {
for (int i=0; i < 5; i++) {
m("写锁" + i);
}
} finally {
w.unlock();
}
},"t2").start();
}
}
- 查看打印,写锁先获取锁,则先执行写锁业务代码
- 读锁先获取锁,则先执行读锁业务代码
6.2 写写互斥
package org.example;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@Slf4j
public class ReadWriteLock {
static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
static Lock r = rwl.readLock();
static Lock w = rwl.writeLock();
public static void m(String str) {
log.info(str);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
//写
new Thread(()->{
log.debug("写锁");
w.lock();
try {
for (int i=0; i < 5; i++) {
m("写锁" + i);
}
} finally {
w.unlock();
}
},"t1").start();
//写
new Thread(()->{
log.debug("写锁");
w.lock();
try {
for (int i=0; i < 5; i++) {
m("写锁" + i);
}
} finally {
w.unlock();
}
},"t2").start();
}
}
- 查看打印,t2写锁先获取锁,先执行t2线程业务代码
6.3 读读并发
- 假设有6个线程,t1正在持有锁,队列内有5个线程(t2–t6)正在等待t1释放锁;
- 当t1释放锁,按照FIFO的原则依次唤醒队列内的线程;
- 如果t2是写锁,则t3继续等待,当t2释放锁后,t3获得锁;
- 如果t3是读锁,那t3会判断t4是不是读锁,如果t4也是读锁,会把t4唤醒,t4唤醒后也会判断t5(假设t5不是读锁)是不是读锁,依此类推,直到后面的锁不是读锁为止,这时t3和t4并发执行;
- 如果t5是写锁,t6是读锁,t6也只能等到t5释放锁后,再执行;
- 读读并发指的是连续的读锁可以并发执行。
package org.example;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@Slf4j
public class ReadWriteLock {
static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
static Lock r = rwl.readLock();
static Lock w = rwl.writeLock();
public static void m(String str) {
log.info(str);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
//读
new Thread(()->{
log.debug("读锁");
r.lock();
try {
for (int i=0; i < 5; i++) {
m("读锁" + i);
}
} finally {
r.unlock();
}
},"t1").start();
//读
new Thread(()->{
log.debug("读锁");
r.lock();
try {
for (int i=0; i < 5; i++) {
m("读锁" + i);
}
} finally {
r.unlock();
}
},"t2").start();
}
}
- 查看打印,t1和t2并发执行
6.4 读写支持重入,但是只支持降级,不支持升级
- 先写锁(降级),在读锁
package org.example;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@Slf4j
public class ReadWriteLock {
static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
static Lock r = rwl.readLock();
static Lock w = rwl.writeLock();
public static void main(String[] args) {
new Thread(()->{
try {
w.lock();
log.debug("write已经获取");
r.lock();
log.debug("read已经获取");
} finally {
r.unlock();
w.unlock();
}
},"t1").start();
}
}
- 查看打印
- 先读锁,在写锁
package org.example;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@Slf4j
public class ReadWriteLock {
static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
static Lock r = rwl.readLock();
static Lock w = rwl.writeLock();
public static void main(String[] args) {
new Thread(()->{
try {
r.lock();
log.debug("read已经获取");
w.lock();
log.debug("write已经获取");
} finally {
w.unlock();
r.unlock();
}
},"t1").start();
}
}
- 查看打印,写锁不能获取
- 锁升级(先读后写),会发生死锁
- 假设有2个线程,如果线程t1先获取读锁,这时候线程t2来了,因为读读并发,所以t2也获取读锁;
- t1继续执行,尝试获取写锁,因为读写互斥,t1一定阻塞,需要等待t2释放读锁,然后t1才能获取写锁,但是t2正常情况下,不会主动释放读锁,所以相互等待。
6.5 缓存场景应用
package org.example;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@Slf4j
public class ReadWriteLock {
//缓存变量
static Object data = "我是缓存中的数据";
//过期标志,true没过期,直接走缓存;false过期,先查数据库更新缓存,再走缓存
static volatile boolean cacheValid = false;
static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
static Lock r = rwl.readLock();
static Lock w = rwl.writeLock();
public static void main(String[] args) {
r.lock();
//如果缓存过期,则查数据库;没过期,则直接使用data
if (!cacheValid) {
//要拿写锁,需要先释放读锁,因为不支持重入升级
r.unlock();
//要去load真实数据,set给缓存拿到写锁
w.lock();
try {
//双重校验
if (!cacheValid) {
//更新缓存
data = "我是数据库中的数据";
cacheValid = true;
}
//更新缓存后,需要读取数据,所以加读锁
r.lock();
} finally {
w.unlock();
}
}
try {
//使用缓存数据
System.out.println(data);
} finally {
r.unlock();
}
}
}
7. Lock与synchronized区别
1、synchronized是Java中的关键字;Lock是jdk中的一个interface接口
2、synchronized修饰的代码在执行异常时,jdk会自动释放锁;Lock发生异常时,需要手动释放,否则会造成死锁,一般都是在finally中
3、synchronize可以用在代码块上,方法上;lock只能写在代码里,不能直接修改方法
4、synchronize是非公平锁;Lock支持公平锁,默认非公平锁
5、synchronize不可中断,只能等待锁的释放;ReentrantLock提供了lockInterruptibly()的打断功能,中断直接抛出异常,可以对打断做出响应
6、synchronized使用Object对象本身的wait、notify、notifyAll调度机制,要么随机唤醒,要么全部唤醒;Lock支撑多条件,可以精准唤醒
7、synchronized是独占锁;ReentrantLock支持读写锁,读锁可以并发,写锁只能独占,还支持锁降级
性能几乎一样,但是在jdk1.8之前,ConcurrentHashMap使用Lock加锁,jdk1.8之后,就换成synchronized,姑且猜测synchronized的性能可能还高于Lock