一、死锁
所谓死锁,就是多个线程竞争相同资源引起的。
举个例子,打羽毛球需要两样东西,羽毛球(锁对象A)和羽毛球拍(锁对象B)。现在有两个人(两个进程),其中一个人有羽毛球拍没有羽毛球,另一个人有羽毛球没有羽毛球拍;他们两个人都在等待另一样打羽毛球的必需品,于是都各各自等待,谁也不能打羽毛球。
(借用网上大佬的图片)
这就是死锁出现的条件
下面是仿照别的大佬的死锁示例
static Object o1 = new Object();
static Object o2 = new Object();
public static void main(String[] args){
Thread t1 = new Thread(){
@Override
public void run(){
synchronized (o1){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1 ---get o1");
}
synchronized (o2){
System.out.println("t1 ---get o2" );
}
}
};
Thread t2 = new Thread(){
@Override
public void run(){
synchronized (o2){
System.out.println("t2---get o2");
synchronized (o1){
System.out.println("t2 get o1");
}
}
}
};
t1.start();
t2.start();
}
死锁的产生就是由于t1线程一开始占有o1,t2线程此时占有o2,t1线程进行中想要获得o2对象锁,而同时t2线程想要获得o1对象锁。但由于两个对象锁此时均被占有,所以线程必须一直等待,导致死锁。
定位死锁
检测死锁可以使用jconsole工具,或者使用jps定位进程id,在使用jstack定位死锁。
二、活锁
活锁出现在两个线程相互改变对方的结束条件,最后谁也无法结束
static Object o1 = new Object();
static volatile int count = 10;
public static void main(String[] args){
new Thread(()->{
while (count>0){
try {
Thread.sleep(1);
count--;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(()->{
while (count<20){
try {
Thread.sleep(1);
count++;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
该情况就是非常典型的活锁状态导致永远无法退出线程。
第一个线程的结束条件是count<0,并在循环中进行自减操作;第二个线程结束条件是count>20,并在循环中进行自加操作。但由于两个线程并发控制同一volatile变量(可见性保证),导致自加和自减操作交替进行,两个线程永远无法退出。
注意,这里使用volatile生命变量,若不使用volatile,则不一定会出现活锁现象,因为volatile具有可见性,即任何一个线程对变量的改变其他线程均可见。
三、饥饿
一个线程由于优先级太低,始终得不到 CPU 调度执行,也不能够结束。
饥饿是由于线程本身优先级问题导致的,在读写锁处容易产生。