一.Lock接口

简介、地位、作用

  1. 1.锁是一种工具,用于控制对共享资源的访问。
  2. 2.Locksynchronized,这两个是最常见的锁,它们都可以达到线程安全的目的,但是在使用上和功能上又有较大的不同。
  3. 3.Lock并不是用来代替synchronized的,而是当使用synchronized不合适或不足以满足要求的时候,来提供高级功能的。
  4. 4.Lock接口最常见的实现类是ReentrantLock
  5. 5.通常情况下,Lock只允许一个线程来访问这个共享资源。不过有的时候,一些特殊的实现也可允许并发访问,比如ReadWriteLock里面的ReadLock

为什么synchronized不够用?为什么需要Lock ?

  1. 1) 效率低∶锁的释放情况少、试图获得锁时不能设定超时、不能中断一个正在试图获得锁的线程。
  2. 2) 不够灵活(读写锁更灵活)加锁和释放的时机单一,每个锁仅有单一的条件(某个对象)可能是不够的
  3. 3) 无法知道是否成功获取到锁

方法介绍

void lock()

  1. public class MustUnlock {
  2. private static Lock lock=new ReentrantLock();
  3. public static void main(String[] args) {
  4. lock.lock();
  5. try {
  6. System.out.println("获取了锁!");
  7. }finally {
  8. lock.unlock();
  9. System.out.println("释放锁对象!");
  10. }
  11. }
  12. }

lock方法不能被中断,一旦陷入到死锁,lock()就会陷入到永久等待。所以一定要finally释放锁。

boolean tryLock();

tryLock()用来尝试获取锁,如果当前锁没有被其他线程占用,则获取成功,则返回true,否则返回false,代表获取锁失败。

boolean tryLock(long time, TimeUnit unit)

相当于在tryLock()方法的基础上加了等待时间,以下示例演示了如何避免死锁的问题。

public class TryLockDeadLock implements Runnable{
    private static Lock lock1=new ReentrantLock();
    private static Lock lock2=new ReentrantLock();
    private int flag;

    public static void main(String[] args) {
        TryLockDeadLock r1 = new TryLockDeadLock();
        TryLockDeadLock r2 = new TryLockDeadLock();
        r1.flag = 0;
        r2.flag = 1;
        new Thread(r1).start();
        new Thread(r2).start();

    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (flag==0){
                try {
                    if (lock1.tryLock(800,TimeUnit.MILLISECONDS)){
                        try {
                            System.out.println("线程1获取了锁1!");
                            Thread.sleep(new Random().nextInt(1000));
                            if (lock2.tryLock(800,TimeUnit.MILLISECONDS)){
                                try {
                                    System.out.println("线程1获取了锁2!");
                                    System.out.println("线程1获取到了两把锁");
                                    break;
                                }finally {
                                    lock2.unlock();
                                    System.out.println("线程1释放了锁2");
                                }
                            }else{
                                System.out.println("线程1获取锁2失败!重试。。");
                            }

                        }finally {
                            lock1.unlock();
                            System.out.println("线程1释放了锁1!");
                        }
                    }else{
                        System.out.println("线程1获取锁1失败!重试。。");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            if (flag==1){
                try {
                    if (lock2.tryLock(800,TimeUnit.MILLISECONDS)){
                        try {
                            System.out.println("线程2获取了锁2");
                            Thread.sleep(new Random().nextInt(1000));
                            if (lock1.tryLock(800,TimeUnit.MILLISECONDS)){
                                try {
                                    System.out.println("线程2获取了锁1!");
                                    System.out.println("线程2获取了两把锁");
                                    break;
                                }finally {
                                    lock1.unlock();
                                    System.out.println("线程2释放了锁1");
                                }
                            }else{
                                System.out.println("线程2获取锁1失败!重试。。");
                            }
                        }finally {
                            lock2.unlock();
                            System.out.println("线程2释放了锁2");
                        }
                    }else{
                        System.out.println("线程2获取锁2失败。重试。。");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }


    }
}

两个线程抢占锁的使用权,如果失败以后对重试。

void lockInterruptibly()

相当于tryLock(long time, TimeUnit unit)把超时时间设置为无限。在等待锁的过程中,线程可以被中断。
以下代码演示了不同打断时机会造成不同的异常:

public class LockInterruptibly implements Runnable {
    private static Lock lock=new ReentrantLock();
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "尝试获取锁");
        try {
            lock.lockInterruptibly();
            try {
                System.out.println(Thread.currentThread().getName() + "获取到了锁");
                //执行业务逻辑
                Thread.sleep(5000);
            }catch (Exception e){
                System.out.println(Thread.currentThread().getName()+"已经获取了锁,但是在执行业务逻辑时发生异常!");
            } finally {
                lock.unlock();
                System.out.println(Thread.currentThread().getName()+"线程释放!");
            }
        } catch (InterruptedException e) {
            System.out.println("获取锁的时候就被打断,发生了异常。");
        }


    }

    public static void main(String[] args) throws InterruptedException {
        LockInterruptibly lockInterruptibly = new LockInterruptibly();
        Thread thread0 = new Thread(lockInterruptibly);
        Thread thread1 = new Thread(lockInterruptibly);
        thread0.start();
        thread1.start();
        Thread.sleep(1000);
        thread1.interrupt();

    }


}

结果有如下两种:
结果1:
Thread-1尝试获取锁
Thread-0尝试获取锁
Thread-1获取到了锁
Thread-1已经获取了锁,但是在执行业务逻辑时发生异常!
Thread-1线程释放!
Thread-0获取到了锁
Thread-0线程释放!

-----------
结果2:
Thread-0尝试获取锁
Thread-1尝试获取锁
Thread-0获取到了锁
Thread-1获取锁的时候就被打断,发生了异常。
Thread-0线程释放!

实际业务场景之中,我们需要针对不同catch去做不同的处理。如果代码改成如下,会发生两种异常情况。

可以看到,两种情况分别如下:
1.当线程0获取了锁,执行业务逻辑时线程1发生了打断,这个时候线程0会正常执行,但是线程1此时还没有获取到锁对象,所以会报没有获取到锁对象的错误。
2.当线程1获取了锁对象,在执行业务逻辑时被打断,此时会报错已经获取了锁对象,但在业务处理中被打断了,此时线程0在线程1释放掉锁以后正常执行。

可见性保证


锁的分类
乐观锁和悲观锁
可重入锁和非可重入锁,已ReentrantLock为例(重点)
公平锁和非公平锁
共享锁和排它锁:以ReentrantReadWriteLock读写锁为例(重点)
自旋锁和阻塞锁
可中断锁∶顾名思义,就是可以响应中断的锁
锁优化