文章目录
🐠一、多把锁
代码:
public class ManyLocks {
public static void main(String[] args) {
BigRoom bigRoom = new BigRoom();
new Thread(() -> {
bigRoom.study();
},"小女").start();
new Thread(() -> {
bigRoom.sleep();
},"小南").start();
}
}
@Slf4j(topic = "BigRoom")
class BigRoom
{
private final Object studyRoom=new Object();
private final Object bedRoom=new Object();
public void study(){
synchronized (studyRoom) {
log.debug("study 2小时");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public void sleep(){
synchronized (bedRoom) {
log.debug("sleeping 2小时");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
结果:
这里我们可以看到小南小女分别在两间房间里头,小南在睡觉,小女在学习,这就会存在多把锁的情况。
🐟二、死锁
1、死锁演示
有这样的情况:一个线程需要同时获得多把锁,这时就容易发生死锁。
例如t1线程获得A对象的锁,接下来又想获取B对象的锁。
t2线程获得到B对象的锁,接下来又想获取A对象的锁。
同样是小南小女,我们来看一下他们的这种情况。
public class ManyLocks {
public static void main(String[] args) {
BigRoom bigRoom = new BigRoom();
new Thread(() -> {
bigRoom.study();
},"小女").start();
new Thread(() -> {
bigRoom.sleep();
},"小南").start();
}
}
@Slf4j(topic = "BigRoom")
class BigRoom
{
private final Object studyRoom=new Object();
private final Object bedRoom=new Object();
public void sleep(){
synchronized (studyRoom) {
log.debug("study 2小时");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (bedRoom){
log.debug("sleeping 2小时");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public void study(){
synchronized (bedRoom) {
log.debug("sleeping 2小时");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (studyRoom){
log.debug("study 2小时");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}
结果:
小女线程在学习了两个小时后,想去bedroom睡觉,却发现小南在bedroom当中占用着锁。而小南在bedroom睡觉两个小时之后,想去studyroom学习,却发现小女在studyroom中学习,并且占用着锁。这样就会导致小南和小女各自占用着锁都不释放,从而导致线程一直无法结束,造成死锁的现象。
2、定位死锁
首先我们打开jconsole
然后我们连接到当前正在跑的进程,我们可以点击检测死锁
🐬三、活锁
活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束。
@Slf4j(topic = "TestLiveLock")
public class TestLiveLock {
static volatile int count=10;
public static void main(String[] args) {
new Thread(() -> {
while(count>0){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
count--;
log.debug("count:{}",count);
}
},"t1"
).start();
new Thread(() -> {
while(count<20){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
count++;
log.debug("count:{}",count);
}
},"t2"
).start();
}
}
执行结果:
我们看到这里我们有t1和t2两个线程,当count>0的时候t1线程一直在减一,当count<20的时候t2线程一直在加一,这样就会导致两个线程一直都无法结束。这就是活锁。
🐳 四、ReentrantLock简介
在Java中,ReentrantLock 是一个可重入的互斥锁,它提供了与synchronized关键字类似的线程同步机制。相对于synchronized块,ReentrantLock 提供了更灵活的锁定机制,并支持更多的功能。
相对于synchronized它具备如下特点
1、可中断
2、可以设置超时时间
3、可以设置为公平锁
4、支持多个条件变量
与synchronized一样,都支持可重入
语法:
//获取锁
lock.lock();
try {
//临界区
} finally {
//释放锁
lock.unlock();
}
🐋五、ReentrantLock可重入
演示代码:
@Slf4j(topic = "TestReentrantLock")
public class TestReentrantLock {
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
lock.lock();
try {
log.debug("进入主方法");
m1();
} finally {
lock.unlock();
}
}
public static void m1() {
lock.lock();
try {
log.debug("进入m1");
m2();
} finally {
lock.unlock();
}
}
public static void m2() {
lock.lock();
try {
log.debug("进入m2");
} finally {
lock.unlock();
}
}
}
执行结果:
🦈六、ReentrantLock可打断
演示代码:
@Slf4j(topic = "TestReentrantLock")
public class TestReentrantLock {
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
log.debug("老子不等了");
return;
}
try {
log.debug("获取到锁了");
} finally {
lock.unlock();
}
}, "t1");
new Thread(() -> {
log.debug("我拿到锁了");
lock.lock();
},"t2").start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
t1.start();
t1.interrupt();
}}
结果:
这里可以看到t2线程一直拿着lock的锁不释放,当主线程去打断t1线程的时候就直接不等了,结束了t1线程。这里需要注意的是lock.lockInterruptibly();需要用try catch来进行包裹,这里catch处理的是当受到外部打断的时候,t1线程应该执行的动作。
🐊七、ReentrantLock锁超时
1、tryLock()尝试获取锁
@Slf4j(topic = "TestReentrantLock")
public class TestReentrantLock {
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
new Thread(() -> {
log.debug("尝试获取锁");
if (!lock.tryLock()) {
log.debug("获取不到锁");
return;
}
try {
log.debug("获取到锁了");
} finally {
lock.unlock();
}
}, "t1").start();
}
}
@Slf4j(topic = "TestReentrantLock")
public class TestReentrantLock {
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread thread = new Thread(() -> {
log.debug("尝试获取锁");
if (!lock.tryLock()) {
log.debug("获取不到锁");
return;
}
try {
log.debug("获取到锁了");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
thread.start();
}
}
结果:
2、tryLock()锁超时
@Slf4j(topic = "TestReentrantLock")
public class TestReentrantLock {
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread thread = new Thread(() -> {
log.debug("尝试获取锁");
try {
if (!lock.tryLock(1,TimeUnit.SECONDS)) {
log.debug("获取不到锁");
return;
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
try {
log.debug("获取到锁了");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
thread.start();
}
}
结果:
这里我们设置了t1线程在一秒后如果获取不到锁就会结束掉t1线程。
注意:这里lock.tryLock(1,TimeUnit.SECONDS),需要用try catch来进行捕获,这里catch所处理的代码是当在等待的过程中被打断的时候,应当进行什么样的处理
🐅八、ReentrantLock公平锁
ReentrantLock默认是不公平的
private static ReentrantLock lock = new ReentrantLock(false);
公平锁一般没有必要,会降低并发度,后面分析原理时会讲解。
🐆九、ReentrantLock条件变量
代码演示:
@Slf4j(topic = "TestAwaitSignal")
public class TestAwaitSignal {
static boolean hasCigaratte = false;
static boolean hasTakeOut = false;
static ReentrantLock ROOM = new ReentrantLock();
//等待烟的休息室
static Condition waitCigaratteSet = ROOM.newCondition();
//等待外卖的休息室
static Condition waitTakeOutSet = ROOM.newCondition();
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
ROOM.lock();
try {
log.debug("有烟没?[{}]", hasCigaratte);
while (!hasCigaratte) {
log.debug("没有烟,先歇着");
try {
waitCigaratteSet.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
log.debug("有烟没?[{}]", hasCigaratte);
if (hasCigaratte) {
log.debug("开始干活");
} else {
log.debug("没干成活");
}
} finally {
ROOM.unlock();
}
}
, "小南").start();
new Thread(() -> {
ROOM.lock();
try {
log.debug("有外卖没?[{}]", hasTakeOut);
try {
waitTakeOutSet.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.debug("有外卖没?[{}]", hasTakeOut);
if (hasTakeOut) {
log.debug("开始吃饭");
} else {
log.debug("没吃上饭 ");
}
} finally {
ROOM.unlock();
}
}, "小女").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
ROOM.lock();
try {
hasTakeOut = true;
waitTakeOutSet.signal();
log.debug("来送外卖喽");
} finally {
ROOM.unlock();
}
}, "送外卖喽").start();
}
}
结果: