文章目录
- 1. 简介
- 2. 死锁
- 3. 活锁
- 4. 饥饿
1. 简介
所谓线程的活跃性,我们知道每个线程所要执行的java代码是有限的,在执行一段时间后线程自然会陷入Terminated状态,但由于某些外部原因导致线程一直执行不完,一直处于活跃状态,这就是所谓的线程的活跃性。下面一一介绍导致线程活跃性的情况:
2. 死锁
有这么一种情况,一个线程同时获取多把锁,这时就容易发生死锁:
t1线程获得A对象锁,接下来想获取B对象的锁,t2线程获取B对象的锁,而想要获得A对象的锁。这就是一种死锁情况。
如下面代码就模拟了死锁的情况
@Slf4j
public class Hello{
public static void main(String[] args){
test();
}
public static void test(){
Object A=new Object();
Object B=new Object();
new Thread(()->{
synchronized (A){
try {
Thread.sleep(1000);
log.debug("我已经有了锁A,现在想获取锁B");
synchronized (B){
log.debug("我已经有了锁A,且获取了锁B");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"线程1").start();
new Thread(()->{
synchronized (B){
try {
Thread.sleep(1000);
log.debug("我已经有了锁B,现在想获取锁A");
synchronized (A){
log.debug("我已经有了锁B,且获取了锁A");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"线程2").start();
}
}
发生了死锁我们此时就可以使用死锁一些工具来定位到死锁发生的地方。检测死锁可以使用jconsole工具,或者使用.jps定位进程id,再用jstack定位死锁。
- 获取进程ID
- 查看线程状态
jstack 1757
可以发现已经出现了死锁信息
我们同样可以使用jconsole
jconsole
在死锁中有一个著名的问题叫做哲学家就餐问题
:
当五个哲学家都拿一根筷子就出现了死锁问题。
筷子类:
final class Chopsticks{
String name;
public chopsticks(String name){
this.name=name;
}
@Override
public String toString() {
return "chopsticks{" +
"name='" + name + '\'' +
'}';
}
}
哲学家类:
@Slf4j
final class Philosopher extends Thread{
Chopsticks left;
Chopsticks right;
@Override
public void run() {
while(true){
synchronized (left){
synchronized (right){
try {
eat();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}
public void eat() throws InterruptedException {
log.debug("两只筷子都有了,开始吃了");
Thread.sleep(1000);
}
public Philosopher(Chopsticks left, Chopsticks right,String name){
super(name);
this.left=left;;
this.right=right;
}
}
测试代码:
@Slf4j
public class Hello{
public static void main(String[] args){
Chopsticks c1=new Chopsticks("筷子1");
Chopsticks c2=new Chopsticks("筷子2");
Chopsticks c3=new Chopsticks("筷子3");
Chopsticks c4=new Chopsticks("筷子4");
Chopsticks c5=new Chopsticks("筷子5");
new Philosopher(c1,c2,"哲学家1").start();
new Philosopher(c2,c3,"哲学家2").start();
new Philosopher(c3,c4,"哲学家3").start();
new Philosopher(c4,c5,"哲学家4").start();
new Philosopher(c5,c1,"哲学家5").start();
}
}
jconsole检测死锁:
使用java地可重入锁可以解决该问题
3. 活锁
活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束的情况
@Slf4j
public class Hello{
static volatile int count=10;
static final Object lock=new Object();
public static void main(String[] args){
new Thread(()->{
while(count>0){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
count--;
log.debug("count:{}",count);
}
},"t1").start();
new Thread(()->{
while(count>0){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
count++;
log.debug("count:{}",count);
}
},"t2").start();
}
}
解决方法,让两个线程执行错开
4. 饥饿
饥饿指的是,一个线程由于优先级太低,始终得不到CPU的调度执行,也不能够结束。
回到上面哲学家问题,我们发现哲学家1-4都是顺序获取锁的,到哲学加5是先获取5再获取1,所以他不是按顺序获取的,这里我们改一下代码:
new Philosopher(c1,c5,"哲学家5").start();
此时就不会出现死锁了,但我们发现哲学家5一直没有获取锁,说明它发生了饥饿。