Java多线程_10 线程的锁


死锁

死锁定义
死锁是指两个或多个进程由于竞争资源而造成的一种僵局,若无外力作用,这些进程无法向前推进。

死锁产生原因

  • 竞争资源:竞争不可抢占资源或可消耗性资源时可能会导致死锁。
  • 进程推进顺序不当;

产生死锁必要条件
必要条件:四个条件都满足,才能发生死锁,也必然能发生死锁。

  • 互斥条件:进程之间必须互斥使用某些资源才可能引起死锁;
  • 请求保持:进程已经占有至少一个资源,又提出新的资源请求;
  • 不可抢占:进程已经占有的资源不能被剥夺;
  • 循环等待:死锁时必然形成一个进程——资源环形链。

线程的死锁

当一个线程永远地持有一个锁,并且其它线程都尝试去获得这个锁时,那么它们将永远被阻塞。

如果线程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);

在这里插入图片描述
对于非公平锁我们发现线程是可以抢占资源的,而不是按顺序来的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值