线程活跃性问题
1.介绍
活跃性是指某件正确的事情最终会发生,当某个操作无法继续下去的时候,就会发生活跃性问题。在多线程中一般有死锁、活锁和饥饿问题
2.死锁
介绍
死锁是指两个或两个以上的线程相互等待对方释放已经持有的锁,从而导致所有涉及的线程都无法继续执行的情况
出现条件
这四个条件是形成死锁的必要条件,非充分条件,即同时满足这四个条件也不一定会产生死锁,但是产生了死锁,肯定是满足了这四个条件。只有四个条件之一不满足,肯定不会产生死锁
- 互斥:线程对资源的访问是互斥的,即一次只能一个线程访问资源
- 占有并等待:线程已经占有至少一个资源,并且在等待其他线程占有的资源
- 不可剥夺:资源不能被强制性地从一个线程中剥夺,只能由占有它的线程释放。
- 循环等待条件:有一组等待线程{P0,P1,…,Pn},P0等待的资源为P1占有,P1等待的资源为P2占有,形成一个循环等待链
代码示例
public class DeadLockDemo {
public static void main(String[] args) throws Exception{
Object lock1 = new Object();
Object lock2 = new Object();
Thread t1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("t1获取lock1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {
System.out.println("t1获取lock2");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock2) {
System.out.println("t2获取lock2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
System.out.println("t2获取lock1");
}
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("执行完毕");
}
}
如何避免死锁
- 开放调用:不要在持有锁的情况下调用其他方法,所以要尽量缩小锁的范围,只对共享可变变量的操作上锁即可,这种方法是避免了循环等待条件的形成
- 固定获取资源的顺序:对于线程获取资源的顺序进行限制,可以对资源进行大小排序,获取资源时要从最大的资源开始获取,这种方法也是避免了循环等待条件的形成
- 使用超时锁:超时锁可以在锁使用一段时间后,自动释放锁,这种方法是打破了不剥夺条件
- 每次只获取一个锁:每次只获取一个锁,避免锁顺序死锁,循环等待条件
死锁的检测
省略,在JVM里面写
3.活锁
介绍
所谓活锁,就是两个以上的线程在执行的时候,因为相互谦让资源,结果都拿不到资源,没法运行程序
两个线程互相改变对象的结束条件,最后谁也无法结束
代码示例
/**
* 演示活锁问题
*/
public class LiveLock {
//勺子类
static class Spoon {
//勺子所属者
private Diner owner;
public Spoon(Diner owner) {
this.owner = owner;
}
public Diner getOwner() {
return owner;
}
public void setOwner(Diner owner) {
this.owner = owner;
}
public synchronized void use() {
System.out.printf("%s has eaten!", owner.name);
}
}
//就餐者类
static class Diner {
//就餐者名字
private String name;
//就餐或饥饿
private boolean isHungry;
public Diner(String name) {
this.name = name;
isHungry = true;
}
//就餐者吃饭方法,spoon 勺子,spouse 和我吃饭的人
public void eatWith(Spoon spoon, Diner spouse) {
//如果自己是饥饿的
while (isHungry) {
//判断勺子的持有者是不是自己
if (spoon.owner != this) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
continue;
}
//判断陪我吃饭的人是不是饥饿,如果是就把勺子给他
if (spouse.isHungry) {
System.out.println(name + "亲爱的" + spouse.name + "你先吃");
spoon.setOwner(spouse);
continue;
}
spoon.use();
isHungry = false;
System.out.println(name + ":我吃完了");
//吃完吧勺子给对方
spoon.setOwner(spouse);
}
}
}
public static void main(String[] args) {
Diner husband = new Diner("牛郎");
Diner wife = new Diner("织女");
Spoon spoon = new Spoon(husband);
new Thread(new Runnable() {
@Override
public void run() {
husband.eatWith(spoon, wife);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
wife.eatWith(spoon, husband);
}
}).start();
}
}
避免死锁方式(加入随机因素)
//判断陪我吃饭的人是不是饥饿,如果是就把勺子给他
//用随机数是其产生一个打破僵局的可能
Random random = new Random();
if (spouse.isHungry && random.nextInt(10)<9) {
System.out.println(name + "亲爱的" + spouse.name + "你先吃");
spoon.setOwner(spouse);
continue;
}
4.饥饿
介绍
当线程需要某些资源(例如CPU),但是却始终得不到,造成一直得不到执行
产生的情况
- 高优先级线程吞噬所有的低优先级线程的CPU时间
- 线程被永久堵塞在一个等待进入同步块的状态,因为其他线程总是能在它之前持续地对该同步块进行访问
解决思路
- 保证资源充足:上面的例子就是不要只有一个饭勺
- 公平地分配资源:把非公平换为公平,让每个线程都有执行到的机会