死锁
死锁定义 死锁是指两个或多个进程由于竞争资源而造成的一种僵局,若无外力作用,这些进程无法向前推进。
死锁产生原因
- 竞争资源:竞争不可抢占资源或可消耗性资源时可能会导致死锁。
- 进程推进顺序不当;
产生死锁必要条件 必要条件:四个条件都满足,才能发生死锁,也必然能发生死锁。
- 互斥条件:进程之间必须互斥使用某些资源才可能引起死锁;
- 请求保持:进程已经占有至少一个资源,又提出新的资源请求;
- 不可抢占:进程已经占有的资源不能被剥夺;
- 循环等待:死锁时必然形成一个进程——资源环形链。
线程的死锁
当一个线程永远地持有一个锁,并且其它线程都尝试去获得这个锁时,那么它们将永远被阻塞。
如果线程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();
}
}
}
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;
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);
}
}
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也就是用队列实现的公平锁和非公平锁:
在公平的锁中,如果有另一个线程持有锁或者有其他线程在等待队列中等待这个锁,那么新发出的请求的线程将被放入到队列中。
而非公平锁上,只有当锁被某个线程持有时,新发出请求的线程才会被放入队列中(此时和公平锁是一样的)。所以,它们的差别在于非公平锁有更多的机会去抢占锁。
模拟售票系统:
public class UserRun implements Runnable {
private int count = 10;
private boolean flag = true;
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() + "谢谢您");
}
}
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);
对于非公平锁我们发现线程是可以抢占资源的,而不是按顺序来的。
|