1. 为什么要使用多线程?使用多线程可能带来什么问题?
使用多线程是为了提高程序的执行效率和运行速度。
但是如果使用不当的话,不仅不能提高执行效率,还会造成很多问题,比如:线程不安全、内存泄露,死锁等。
2. 造成线程死锁的原因有哪些?
线程死锁:多个线程同时被阻塞,他们中的一个或者多个都在等待某个资源被释放。由于线程被无限期阻塞,因此程序不可能被正常终止。
比如线程A拥有资源2,线程B拥有资源1,他们都想要对方的资源,但是在获取到需要的资源之前都没有释放自己本来拥有的资源,这就导致了这两个线程都在相互等待而进入死锁状态。
代码如下所示:
public class DeadLockDemo {
public static void main(String[] args) {
String lockA="lockA";
String lockB="lockB";
new Thread(new HoldLockThread(lockA,lockB),"Thread AAA").start();
// new Thread(new HoldLockThread(lockA,lockB),"Thread BBB").start();
new Thread(new HoldLockThread(lockB,lockA),"Thread BBB").start();
}
}
class HoldLockThread implements Runnable{
private String lookA;
private String lookB;
public HoldLockThread(String lookA, String lookB) {
this.lookA = lookA;
this.lookB = lookB;
}
@Override
public void run() {
synchronized (lookA){
System.out.println(Thread.currentThread().getName()+"\t 自己持有:"+lookA+"\t 尝试获得:"+lookB);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lookB){
System.out.println(Thread.currentThread().getName()+"\t 已获得:"+lookA+"和"+lookB);
}
System.out.println("用完,释放:"+lookA+"和"+lookB);
}
}
}
Thread AAA 自己持有:lockA 尝试获得:lockB
Thread BBB 自己持有:lockB 尝试获得:lockA
-
线程 A 通过 synchronized (lookA) 获得 lookA的监视器锁,然后通过
TimeUnit.SECONDS.sleep(2);
让线程 A 休眠 2s。 -
为的是让线程 B 得到执⾏然后获取到 lookB的监视器锁。线程 A 和线程 B休眠结束了都开始企图请求获取对⽅的资源,然后这两个线程就会陷⼊互相等待的状态,这也就产⽣了死锁。
如果不想产生死锁,可以让B先获取lockA,再去获取lockB资源,这时候因为lockA被A占用,所以B只能等待A用完以后,才可以去获得资源。
Thread AAA 自己持有:lockA 尝试获得:lockB
Thread AAA 已获得:lockA和lockB
用完,释放:lockA和lockB
Thread BBB 自己持有:lockA 尝试获得:lockB
Thread BBB 已获得:lockA和lockB
用完,释放:lockA和lockB
2.1 产⽣死锁必须具备以下四个条件:
- 互斥条件 :该资源任意⼀个时刻只由⼀个线程占⽤。
- 请求与保持条件 :⼀个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件 :线程已获得的资源在末使⽤完之前不能被其他线程强⾏剥夺,只有⾃⼰使⽤完
毕后才释放资源。 - 循环等待条件 :若⼲进程之间形成⼀种头尾相接的循环等待资源关系。
2.2 如何避免线程线程死锁呢?
- 破坏互斥条件 :这个条件我们没有办法破坏,因为我们⽤锁本来就是想让他们互斥的(临界
资源需要互斥访问)。 - 破坏请求与保持条件 :⼀次性申请所有的资源。
- 破坏不剥夺条件 :占⽤部分资源的线程进⼀步申请其他资源时,如果申请不到,可以主动释
放它占有的资源。 - 破坏循环等待条件 :靠按序申请资源来预防。按某⼀顺序申请资源,释放资源则反序释放。
破坏循环等待条件。