JavaSE-Lock锁的获取和死锁

        这篇博客主要总结死锁以及Lock锁的获取

多线程死锁

        Java的线程死锁是一个十分经典的问题,因为不同的线程都在等待根本不可能被释放的锁,从而导致所有任务都无法继续完成,就会出现死锁的情况。所以,在多线程编程中,”死锁“是必须要避免的,因为会造成程序的卡死。

        首先先举一个死锁的例子:

class MyThread implements Runnable{
    public String name;
    public Object lock1 = new Object();
    public Object lock2 = new Object();

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        if("a".equals(name)){
            synchronized (lock1){
                try{
                    System.out.println("name = "+this.name);
                    Thread.sleep(2000);
                }catch (InterruptedException i){
                    i.printStackTrace();
                }
                synchronized (lock2){
                    System.out.println("按照lock1->lock2的顺序");
                }
            }
        }
        if("b".equals(name)){
            synchronized (lock2){
                try{
                    System.out.println("name = "+this.name);
                    Thread.sleep(2000);
                }catch (InterruptedException i){
                    i.printStackTrace();
                }
                synchronized (lock1){
                    System.out.println("按照lock2->lock1的顺序");
                }
            }
        }
    }
}

public class Main{
    public static void main(String[] args){
        try{
            MyThread myThread = new MyThread();
            myThread.setName("a");
            Thread thread = new Thread(myThread);
            thread.start();
            Thread.sleep(500);
            myThread.setName("b");
            Thread thread1 = new Thread(myThread);
            thread1.start();
        }catch (InterruptedException i){
            i.printStackTrace();
        }
    }
}

        先解释一下这段代码:一开始只有一个线程进入run方法,在这个时候name = a;执行输出语句,执行完之后进入两秒的sleep等待时间,在这个时候该线程持有lock1的锁。在这时,另一个线程也进入了run方法,但是这个时候name = b;继续执行输出语句,另一个线程持有的是lodk2的锁。我们这个时候会发现:线程1想要执行下面的语句,需要获得lock2的锁,但是这个锁被另一个线程所持有。同理,另一个线程也想获得lock1的锁。所以他们互相拿着对方需要的锁。造成了程序的卡死。这就是一个很简单的死锁的例子。

Lock锁:

        在JDK1.5以后,引入了java.util.concurrent.locks包,新提供了一种实现同步访问的方式:Lock。

        其实synchronized实现同步是有一定的缺陷的,所以我先总结一下synchronized的缺陷

        首先回顾一下synchronized的知识:如果一个方法或者是代码块被synchronized修饰了,如果这个时候有线程获取的对应的锁执行该代码块或者方法时。其他的线程只能一直等待锁被释放。就是只能等代码块被执行完或者是那个线程执行出现了异常,JVM让它释放锁。

        假设这个线程因为调用sleep方法或者IO操作被阻塞住了,因为锁还没有被释放,所以其他的线程只能一直等待,这其实是很影响程序执效率的一种情况。那么,必须要有一种机制可以避免这种让其他线程无限期等待下去的情况。JDK1.5之后引入的Lock锁就可以实现我们的这种需求。

        另外,synchronized是没有办法知道线程是否成功获取到锁的,但是Lock锁可以。也就是说Lock锁的功能是比synchronized多的。除此之外,synchronized是Java的关键字,是内置特性。但是Lock是类,同步访问是通过这个类来实现的。而且,synchronized是不需要用户去手动释放锁的,当synchronized方法或者代码块执行完毕之后,锁会自动被释放。但是Lock锁必须用户自己去释放,如果没有释放则可能会出现死锁的现象。

Java.ntil.concurrent.locks包

        Lock接口

        首先看一下Lock的源码:

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

        newCondition()方法和线程协作有关,现在先不做解释。

        lock(),tryLock(),tryLock(long time,TimeUnit unit),lockInterruptibly()这四种方法是用来获取锁的。只有unLock方法是用来释放锁的。

        由于使用Lock锁时必须手动释放锁,但是在发生了异常时,不会自动释放锁。所以使用Lock锁时应该放在try...catch块里进行,为了保证锁一定被释放,释放锁的操作应该放在finally块里执行。如果使用lock()方法上锁的话,举一个例子。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class MyThread implements Runnable{
    private Lock aLock = new ReentrantLock();

    @Override
    public void run() {
        aLock.lock();//上锁
        try{
            System.out.println("测试");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            aLock.unlock();//解锁
        }
    }
}

        tryLock():该方法有返回值,如果获取成功返回true否则返回false,就是说如果拿不到锁也不会继续等待。这一点比使用synchronized方便许多。

        tryLock(long time,TimeUnit unt)方法和tryLock()方法类似,使用这个方法如果拿不到锁,还是会等待一段时间的,在这个时间内如果拿不到锁,就返回false,如果在这段时间内拿到了锁,就会返回true。

        在使用tyrLock获取锁的时候一般是这么写的

    @Override
    public void run() {
        if(aLock.tryLock()) {//尝试获取锁
            try {
                System.out.println("测试");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                aLock.unlock();
            }
        }else {
            //拿不到锁的话就干别的事
        }
    }

        lockInterruptibly():在通过这个方法来获取锁时,如果线程正在等待获取锁,那么这个线程可以相应中断,就是我们随时可以中断该线程的等待状态。假设有两个线程同时通过该方法想获取某个锁时,假设其中一个线程拿到了锁,则我们可以随时对另一个线程调用interrupt()方法终止它的等待过程。

        lockInterruptibly会抛出InterruptedException异常,所以我们在使用该方法时应该这么写

    public void methpod()throws InterruptedException{
        aLock.lockInterruptibly();
        try{
            //......
        }finally {
            aLock.unlock();
        }
    }

        在这里需要注意的是:如果线程已经拿到了锁,就不能通过interrupt()方法中断。因为它只能中断阻塞过程中的线程。

        使用此方法获取锁,和synchronized获取锁的不同在于:synchronized是不能把在等待状态下的线程中断的,但是这个方法可以。

        先初步总结到这里,后续会有补充。

        





猜你喜欢

转载自blog.csdn.net/qq_38449518/article/details/80154239