死锁
死锁定义
死锁是指两个或多个进程由于竞争资源而造成的一种僵局,若无外力作用,这些进程无法向前推进。
死锁产生原因
- 竞争资源:竞争不可抢占资源或可消耗性资源时可能会导致死锁。
- 进程推进顺序不当;
产生死锁必要条件
必要条件:四个条件都满足,才能发生死锁,也必然能发生死锁。
- 互斥条件:进程之间必须互斥使用某些资源才可能引起死锁;
- 请求保持:进程已经占有至少一个资源,又提出新的资源请求;
- 不可抢占:进程已经占有的资源不能被剥夺;
- 循环等待:死锁时必然形成一个进程——资源环形链。
线程的死锁
当一个线程永远地持有一个锁,并且其它线程都尝试去获得这个锁时,那么它们将永远被阻塞。
如果线程A持有锁L并且想获得锁M,线程B持有锁M并且想获得锁L,那么这两个线程将永远等待下去,这种情况就是最简单的死锁形式。
public class DeadLock {
//定义两个资源
private final Object left = new Object();
private final Object right = new Object();
public void leftRight() throws Exception{
synchronized (left){
//让线程休眠,否则先运行的线程有可能获得两个锁了
Thread.sleep(2000);
synchronized (right){
System.out.println("leftRight end!");
}
}
}
public void rightLeft() throws Exception{
synchronized(right){
Thread.sleep(2000);
synchronized (left){
System.out.println("rightLeft end!");
}
}
}
}
定义两个依赖该资源的线程:
public class Thread0 extends Thread{
private DeadLock dl;
public Thread0(DeadLock dl){
this.dl = dl;
}
@Override
public void run() {
try {
dl.leftRight();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class Thread1 extends Thread {
private DeadLock dl;
public Thread1(DeadLock dl){
this.dl = dl;
}
@Override
public void run() {
try {
dl.rightLeft();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* @Author: 凤文
* @CreateTime: 2021/11/27 13:32
* @Description: 线程的死锁
* 本例结果是 System.out.println("leftRight end!")结果迟迟不出来,程序一直在运行
* 这时因为执行它们所需要的对象被这两个线程锁住了,都拿不到自己需要的对象,这就是死锁。
*/
public class Test {
public static void main(String[] args) {
DeadLock dl = new DeadLock();
new Thread0(dl).start();
new Thread1(dl).start();
}
}
我们在启动这两个线程的时候,任务中需要left和right的资源,刚开始的时候两个线程各抢到一个资源,假如Thread0线程抢到了left,Thread1线程抢到了right。两秒后,两个线程需要继续推进,Thread0需要拿到right资源才能向前推进,此时right资源被Thread1占用着,所以Thread0只能等待,Thread1也是同理,两个线程的所需要的资源都被对方持有且不放,导致两个线程一直等待,线程无法向前推进,这就是一个最简单的死锁。
线程的明锁
Java5提供了锁对象Lock,利用锁可以方便地实现资源的封锁,用来对竞争资源并发访问的控制。Lock所有加锁和解锁的方法都是显示的。
方法 | 说明 |
---|---|
Lock.lock() | 获取锁 |
Lock.unlock() | 释放锁 |
Lock可以构建公平锁和非公平锁,默认是非公平锁。 |
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author: 凤文
* @CreateTime: 2021/11/27 13:59
* @Description:
* 我们可以在operator方法中加synchronized关键字
* 对于明锁,我们还可以使用Lock也能达到同样的目的
*/
public class Count {
private int num;
/**
* 锁对象
*/
private Lock lock = new ReentrantLock();
public Count(int num) {
this.num = num;
}
public void operator(int operatorNum) {
lock.lock();
this.num += operatorNum;
System.out.println(Thread.currentThread().getName()
+ "操作的数量是" + operatorNum + "," + "操作后现在的数量是:" + num);
lock.unlock();
}
}
public class ThreadOperator extends Thread{
private int operatorNum;
private Count c;
public ThreadOperator(int operatorNum, Count c,String threadName) {
super(threadName);
this.operatorNum = operatorNum;
this.c = c;
}
@Override
public void run() {
c.operator(operatorNum);
}
}
/**
* @Author: 凤文
* @CreateTime: 2021/11/27 13:56
* @Description: 线程的明锁
* synchronized是Java关键字,是内置的特性,属于隐式的锁
* 而Java中还有明锁,提供了锁对象Lock,所有的加锁和解锁的方法都是显示的。
* Lock().lock():获取锁
* Lock().unlock():释放锁
*
* 本例中初值100,经过5个线程的操作,结果应该为130
* 如果不加锁肯定达不到预期
* 我们可以在operator方法中加synchronized关键字
* 对于明锁,我们还可以使用Lock也能达到同样的目的
*
*/
public class Test {
public static void main(String[] args) {
Count c = new Count(100);
ThreadOperator thread1 = new ThreadOperator(10,c, "线程A");
ThreadOperator thread2 = new ThreadOperator(-20,c, "线程B");
ThreadOperator thread3 = new ThreadOperator(30,c, "线程C");
ThreadOperator thread4 = new ThreadOperator(-40,c, "线程D");
ThreadOperator thread5 = new ThreadOperator(50,c, "线程E");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
thread5.start();
}
}
线程的公平锁与非公平锁
Java的ReenTrantLock也就是用队列实现的公平锁和非公平锁:
在公平的锁中,如果有另一个线程持有锁或者有其他线程在等待队列中等待这个锁,那么新发出的请求的线程将被放入到队列中。
而非公平锁上,只有当锁被某个线程持有时,新发出请求的线程才会被放入队列中(此时和公平锁是一样的)。所以,它们的差别在于非公平锁有更多的机会去抢占锁。
模拟售票系统:
/**
* @Author: 凤文
* @CreateTime: 2021/11/27 14:44
* @Description: 公平锁与非公平锁、模拟购票系统
*/
public class UserRun implements Runnable {
//一共有10张票
private int count = 10;
private boolean flag = true;
//true代表公平锁,false就是非公平锁
private Lock lock = new ReentrantLock(true);
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ",欢迎订票");
while (flag) {
lock.lock();
System.out.println(Thread.currentThread().getName() + "取到了第" + count-- + "张票");
if (count < 1) {
flag = false;
}
lock.unlock();
}
System.out.println(Thread.currentThread().getName() + "谢谢您");
}
}
/**
* @Author: 凤文
* @CreateTime: 2021/11/27 14:50
* @Description: 公平锁与非公平锁
* 当使用公平锁时,每一个线程都要到队列里等着,等资源锁释放时,按顺序来,也就是按照start()调用的顺序
* 而非公平锁不看谁先来,所以非公平锁更有机会获得锁
*/
public class Test {
public static void main(String[] args) {
UserRun run = new UserRun();
Thread t1 = new Thread(run,"东门汽车站");
Thread t2 = new Thread(run,"南门汽车站");
Thread t3 = new Thread(run,"北门汽车站");
t1.start();
t2.start();
t3.start();
}
}
公平锁的结果:
对于公平锁,我们发现它是有顺序的,按照队列中的顺序执行。
将锁换成非公平锁
private Lock lock = new ReentrantLock(false);
对于非公平锁我们发现线程是可以抢占资源的,而不是按顺序来的。