1.线程的活跃性
1>.定义:
线程内的有限代码因为某种原因一直无法执行完毕(/执行不完);
1.1.线程活跃性的现象-死锁
1>.有这样的情况:一个线程需要同时获取多把锁,这时就容易发生死锁;
2>.案例
①.t1线程已经获得A对象锁,接下来想获取B对象的锁;
②.t2线程已经获得B对象锁,接下来想获取A对象的锁;
@Slf4j
public class TestDeathLock {
//两个对象,对应两把锁
final static Object A = new Object();
final static Object B = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (A) {
log.info("已经获取了lock A");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (B) {
//由于对象锁B当前是被线程t2占用,且并没有释放,那么线程t1获取锁失败进入EntryList中等待
log.info("再获取lock B");
log.info("操作...");
}
}
}, "t1");
Thread t2 = new Thread(() -> {
synchronized (B) {
log.info("已经获取了lock B");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (A) {
//由于对象锁A当前是被线程t1占用,且并没有释放,那么线程t2获取锁失败进入EntryList中等待
log.info("再获取lock A");
log.info("操作...");
}
}
}, "t2");
t1.start();
t2.start();
}
}
3>.定位死锁
检测死锁可以使用jconsole工具
,或者使用jps定位进程id,再用jstack定位死锁
,如:
4>.如何避免死锁?
①.避免死锁要注意加锁顺序;
②.另外如果由于某个线程进入了死循环,导致其它线程一直等待,对于这种情况linux下可以通过top先定位到CPU占用高的Java进程,再利用"top -Hp 进程id"来定位是哪个线程,最后再用jstack 排查;
5>.使用顺序加锁
的方式解决之前的死锁问题
①.分析
②.解决方案:
***注意:该方式容易出现饥饿问题,即某个线程可能一直都获取不到锁资源!
1.2.线程活跃性的现象–活锁
1>.活锁: 两个线程互相改变对方的结束条件,最后谁也无法结束(一直在运行);
2>.案例:
@Slf4j
public class TestLiveLock {
//共享变量
static volatile int count = 10;
//对象锁
static final Object lock = new Object();
public static void main(String[] args) {
new Thread(() -> {
// 期望减到 0 退出循环
while (count > 0) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
log.info("count: {}", count);
}
}, "t1").start();
new Thread(() -> {
// 期望超过 20 退出循环
while (count < 20) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
log.info("count: {}", count);
}
}, "t2").start();
}
}
3>.如何避免活锁?
①.保证多个线程的执行时间有一定的间隔(交错),例如增加一些随机的睡眠时间;
1.3.线程活跃性的现象–饥饿
1>.定义: 一个线程由于优先级太低,始终得不到 CPU 调度执行,也不能够结束;
饥饿的情况不易演示,后续在读写锁相关的文章中会涉及饥饿问题!