持续创造,加快成长!这是我参加「日新方案 10 月更文应战」的第13天,点击检查活动详情

一、ReentrantLock是什么

ReentrantLock是一种根据AQS结构的运用完结,是JDK中的一种线程并发访问的同步手法,它的功能相似于synchronized是一种互斥锁,能够确保线程安全

相对于 synchronized, ReentrantLock具备如下特色:

  • 可中止
  • 能够设置超时时刻
  • 能够设置为公正锁
  • 支撑多个条件变量
  • 与 synchronized 一样,都支撑可重入

进入源码能够看到,其完结了公正锁和非公正锁

ReentrantLock并发锁使用详解

内部完结了加锁的操作,并且支撑重入锁。不必我们再重写

ReentrantLock并发锁使用详解

解锁操作

ReentrantLock并发锁使用详解

1-1、ReentrantLock和synchronized差异

synchronized和ReentrantLock的差异:

  • synchronized是JVM层次的锁完结,ReentrantLock是JDK层次的锁完结;
  • synchronized的锁状况是无法在代码中直接判断的,可是ReentrantLock能够通过ReentrantLock#isLocked判断;
  • synchronized是非公正锁,ReentrantLock是能够是公正也能够是非公正的;
  • synchronized是不能够被中止的,而ReentrantLock#lockInterruptibly办法是能够被中止的;
  • 在发生反常时synchronized会主动开释锁,而ReentrantLock需求开发者在finally块中显示开释锁;
  • ReentrantLock获取锁的形式有多种:如立即回来是否成功的tryLock(),以及等候指定时长的获取,愈加灵活;
  • synchronized在特定的情况下对于已经在等候的线程是后来的线程先取得锁(回顾一下sychronized的唤醒策略),而ReentrantLock对于已经在等候的线程是先来的线程先取得锁;

1-2、ReentrantLock的运用

1-2-1、ReentrantLock同步履行,相似synchronized

运用ReentrantLock需求注意的是:必定要在finally中进行解锁,办法事务抛出反常,无法解锁

public class ReentrantLockDemo {
    private static  int sum = 0;
    private static ReentrantLock lock=new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 3; i++) {
            Thread thread = new Thread(()->{
                //加锁
                lock.lock();
                try {
                    // 临界区代码
                    // TODO 事务逻辑:读写操作不能确保线程安全
                    for (int j = 0; j < 10000; j++) {
                        sum++;
                    }
                } finally {
                    // 解锁--必定要在finally中解锁,避免事务代码反常,无法开释锁
                    lock.unlock();
                }
            });
            thread.start();
        }
        Thread.sleep(2000);
        System.out.println(sum);
    }
}

测验成果:

ReentrantLock并发锁使用详解

1-2-2、可重入锁

可重入锁便是 A(加锁)–>调用—>B(加锁)–>调用–>C(加锁),从A到C即便B/C都有加锁,也能够进入

@Slf4j
public class ReentrantLockDemo2 {
    public static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        method1();
    }
    public static void method1() {
        lock.lock();
        try {
            log.debug("execute method1");
            method2();
        } finally {
            lock.unlock();
        }
    }
    public static void method2() {
        lock.lock();
        try {
            log.debug("execute method2");
            method3();
        } finally {
            lock.unlock();
        }
    }
    public static void method3() {
        lock.lock();
        try {
            log.debug("execute method3");
        } finally {
            lock.unlock();
        }
    }
}

履行成果:

ReentrantLock并发锁使用详解

1-2-3、锁中止

能够运用lockInterruptibly来进行锁中止

lockInterruptibly()办法能够中止等候获取锁的线程。当两个线程一起通过lock.lockInterruptibly()获取某个锁时,假若此时线程A获取到了锁,而线程B只要等候,那么对线程B调用threadB.interrupt()办法能够中止线程B的等候进程。

public class ReentrantLockDemo3 {
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        Thread t1 = new Thread(() -> {
            log.debug("t1发动...");
            try {
                lock.lockInterruptibly();
                try {
                    log.debug("t1取得了锁");
                } finally {
                    lock.unlock();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.debug("t1等锁的进程中被中止");
            }
        }, "t1");
        lock.lock();
        try {
            log.debug("main线程取得了锁");
            t1.start();
            //先让线程t1履行
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            t1.interrupt();
            log.debug("线程t1履行中止");
        } finally {
            lock.unlock();
        }
    }
}

履行成果:

ReentrantLock并发锁使用详解

1-2-4、取得锁超时失败

能够让线程等候指定的时刻,假如还未获取锁则进行失败处理。

如下代码,首要让主线程取得锁,然后让子线程发动测验获取锁,可是由于主线程获取锁之后,让线程等候了2秒,而子线程取得锁的超时时刻只要1秒,假如未取得锁,则进行return失败处理

public class ReentrantLockDemo4 {
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        Thread t1 = new Thread(() -> {
            log.debug("t1发动...");
            //超时
            try {
                if (!lock.tryLock(1, TimeUnit.SECONDS)) {
                    log.debug("等候 1s 后获取锁失败,回来");
                    return;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                return;
            }
            try {
                log.debug("t1取得了锁");
            } finally {
                lock.unlock();
            }
        }, "t1");
        lock.lock();
        try {
            log.debug("main线程取得了锁");
            t1.start();
            //先让线程t1履行
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } finally {
            lock.unlock();
        }
    }
}

履行成果:

ReentrantLock并发锁使用详解

1-2-5、公正锁

ReentrantLock 默认是不公正的

ReentrantLock并发锁使用详解

首要发动500次for循环创立500个线程,然后进行加锁操作,并一起发动了。这样这500个线程就顺次排队等候加锁的处理

下面500个线程也是等候加锁操作

假如运用公正锁,下面500的线程只要等上面500个线程运转完结之后才干取得锁。

@Slf4j
public class ReentrantLockDemo5 {
    public static void main(String[] args) throws InterruptedException {
        ReentrantLock lock = new ReentrantLock(true); //公正锁
        for (int i = 0; i < 500; i++) {
            new Thread(() -> {
                lock.lock();
                try {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    log.debug(Thread.currentThread().getName() + " running...");
                } finally {
                    lock.unlock();
                }
            }, "t" + i).start();
        }
        // 1s 之后去争抢锁
        Thread.sleep(1000);
        for (int i = 0; i < 500; i++) {
            new Thread(() -> {
                lock.lock();
                try {
                    log.debug(Thread.currentThread().getName() + " running...");
                } finally {
                    lock.unlock();
                }
            }, "强行刺进" + i).start();
        }
    }
}

测验成果(后进入的线程都在等候排队)

ReentrantLock并发锁使用详解

运用非公正锁的情况下,就能够看到下面500线程有些线程就能够抢占锁了

ReentrantLock并发锁使用详解

那ReentrantLock为什么默认运用非公正锁呢?实际上便是为了提高功能,假如运用公正锁,当时锁对象开释之后,还需求去队列中获取第一个排队的线程,然后进行加锁处理。而非公正锁,可能再当时对象开释锁之后,正好有新的线程在获取锁,这样就能够直接进行加锁操作,不必再去队列中读取。