死锁
检查是否发生了死锁
jstack
通过 线程栈快照 定位线程中出现长时间停顿的原因, jconsole
图像界面 检查是否发生了死锁
死锁的概念
多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。
就是两个线程互相占有自己的锁, 却又尝试获取别人的锁, 导致进入死锁状态.
死锁产生的条件
-
互斥
:进程要求对所分配的资源进行排他性控制, 就是一段时间内某资源仅为一个进程所占用
-
请求和保持
:当进程因请求资源而阻塞时, 对已获得的资源保持不放
-
不可剥夺
:进程已经获得的资源在为使用完之前,不能剥夺,只能在使用完由自己释放
-
环路等待
:发生死锁时, 必然存在一个进程–资源的环形链等待链,链中每一个进程已获得的资源同时被下一个进程所请求.
预防死锁
-
破坏请求条件
:资源一次性分配, 这样就不会再有请求了. 但是资源利用率会降低
-
破坏请求保持条件
:进程运行前一次申请它所需要的全部资源, 在它的资源满足前, 不进行运行。一旦运行,资源将不可剥夺.
这种做法可能严重浪费资源,可能导致饥饿现象.个别线程长时间占用某个资源, 导致该资源的进程迟迟无法运行. -
破坏不可剥夺条件
:进程申请资源一旦被阻塞, 就必须释放已经占有的所有资源.
-
破坏环路等待条件
:系统给每类资源赋予一个编号, 每一个进程按照编号递增的顺序请求资源, 释放则相反.
解决死锁
- 每个线程按照确定的顺序获取锁 , 这样就不会发生一直等待的情况. 但是可能出现饥饿的情况.
- 使用tryLock方法固定时长等待锁, 在线程获取锁超时以后, 主动释放之前已经获得的所有锁.
活锁
概念
任务没有被阻塞, 但是由于某些条件没有满足, 导致一直重复尝试–失败–尝试–失败的过程, 处于活锁的实体不断的在改变状态, 活锁有可能自行解开的.
比如一个进程把一个数加到20就退出 , 一个数减到0就退出 ,他们两个一起执行就不会成功.
解决
解决活锁的一个简单的办法就是在下一次尝试获取资源之前, 随机休眠一段时间.
ReentrantLock
概念
可中断
, 其他线程可以把竞争锁的线程打断, 去处理其他逻辑, 防止一直等待可设置超时时间
, 规定时间内竞争不到锁就放弃, 不像synchroniezd的EntryList一样会一直竞争, 可解决死锁可设置公平锁,
ReentrantLock默认不公平的,需要自己调用构造方法设置,预防饥饿的情况, 遵循先到先得的原则, 防止线程过多的情况 导致有些锁一直拿不到锁可支持多个条件变量
, 就是多个类似于synchroniezd一样的waitset. 以后notifyAll的时候可以选择同一个条件的线程一起唤醒.
一般解决饥饿问题不使用公平锁, 会降低并发度, 应该使用tryLock(Time)方法, 一旦获取锁超时就放弃
可重入
同一个线程如果首次获取了这把锁, 那么因为它是这把锁的拥有者, 因此有权利再次获得这把锁.
不可重入的话就会被锁住
public class ReentrantLockTest {
private static ReentrantLock reentrantLock=new ReentrantLock();
public static void main(String[] args) {
reentrantLock.lock();
try {
System.out.println("进入m1");
m1();
}finally {
reentrantLock.unlock();
}
}
public static void m1(){
reentrantLock.lock();
try {
System.out.println("进入m2");
m2();
}finally {
reentrantLock.unlock();
}
}
public static void m2(){
reentrantLock.lock();
try {
System.out.println("已经在m2");
}finally {
reentrantLock.unlock();
}
}
}
可打断
public class ReentrantLockTest {
private static ReentrantLock reentrantLock=new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
try {
reentrantLock.lockInterruptibly(); //此锁可打断 , 如果仅仅是reentrantLock.lock, 则无法打断
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("锁被打断");
return;
}
System.out.println("竞争锁成功,执行逻辑");
});
reentrantLock.lock(); //主线程获取锁, t1线程就只能竞争 ,并且设置可打断的竞争
t1.start();
TimeUnit.SECONDS.sleep(2);
t1.interrupt(); //打断
}
}
可超时
public class ReentrantLockTest {
private static ReentrantLock reentrantLock=new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
try {
if (!reentrantLock.tryLock(5,TimeUnit.SECONDS)) { //竞争5s锁, 竞争不到返回false
System.out.println("5s了,竞争不到锁,执行其他逻辑");
return;
}
System.out.println("竞争到锁了");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
reentrantLock.lock(); //主线程获取锁, t1线程就只能竞争
t1.start();
}
}
可设置公平锁
一般解决饥饿问题不使用公平锁, 会降低并发度, 应该使用tryLock(Time)方法, 一旦获取锁超时就放弃
条件变量
synchronized是所有不满足条件的线程都在一个WaitSet中等待,唤醒时就全部一起唤醒
而ReentrantLock支持多个专门等待不同条件的ConditionObject,类似于waitset,唤醒时按照特定条件的waitset进行唤醒
public class ReentrantLockTest {
private static ReentrantLock reentrantLock=new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Condition condition1 = reentrantLock.newCondition(); //休息室1
Condition condition2 = reentrantLock.newCondition(); //休息室2
Thread t1=new Thread(()->{
try {
condition1.await(); //线程1进入休息室1等待
condition1.awaitNanos(10); //根据纳秒等待
condition1.awaitUninterruptibly(); // 可打断的等待
condition1.signal(); //唤醒休息室1特定的一个
condition1.signalAll(); //唤醒休息室2所有
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
Thread t2=new Thread(()->{
try {
condition2.await(); //线程2进入休息室2等待
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
}
}