关于java:突击并发编程JUC系列ReentrantLock

14次阅读

共计 4209 个字符,预计需要花费 11 分钟才能阅读完成。

突击并发编程 JUC 系列演示代码地址:
https://github.com/mtcarpenter/JavaTutorial

锁是用来管制多个线程访问共享资源的形式,通过锁能够避免多个线程同时访问共享资源。在 Java1.5之前实现锁只能应用 synchronized关键字实现,然而 synchronized 隐式获取开释锁,在 1.5之后官网新增了 lock 接口也是用来实现锁的性能,,它具备与 synchronized 关键字相似的同步性能,显式的获取和开释锁。lock领有了锁获取与开释的可操作性、可中断的获取锁以及超时获取锁等多种 synchronized 关键字所不具备的同步个性。

LOCK 办法阐明

  • void lock(): 获取锁,调用该办法以后线程将会获取锁,当锁取得后,从该办法返回
  • void lockInterruptibly() throws InterruptedException: 可中断地获取锁,和 lock办法地不同之处在于该办法会响应中断,即在锁的获取中能够中断以后线程
  • boolean tryLock(): 尝试非阻塞地获取锁,调用该办法后立即返回,如果可能获取则返回 true 否则 返回 false
  • boolean tryLock(long time, TimeUnit unit): 超时地获取锁,以后线程在以下 3 种状况下会返回:

    • 以后线程在超时工夫内取得了锁
    • 以后线程在超时工夫被中断
    • 超时工夫完结后,返回 false
  • void unlock(): 开释锁
  • Condition newCondition(): 获取锁期待告诉组件,该组件和以后的锁绑定,以后线程只有取得了锁,能力调用该组件的 wait() 办法,而调用后,以后线程将开释锁。

ReentrantLock 简介

Lock 作为接口类为咱们提供一组办法,只能通过的实现类进行 Lock 办法,明天咱们就讲讲继承 Lock 接口一个可重入的独占锁 ReentrantLock 实现类,ReentrantLock通过自定义队列同步器(Abstract Queued Sychronized,AQS)来实现锁的获取与开释。它应用了一个 int 成员变量示意同步状态,通过内置的 FIFO 队列来实现资源获取线程的排队工作,并发包的作者(Doug Lea)冀望它可能成为实现大部分同步需要的根底。

独占锁指该锁在同一时刻只能被一个线程获取,而获取锁的其余线程只能在同步队列中期待;可重入锁指该锁可能反对一个线程对同一个资源执行屡次加锁操作。ReentrantLock反对偏心锁和非偏心锁的实现。偏心指线程竞争锁的机制是偏心的,而非偏心指不同的线程获取锁的机制是不偏心的。ReentrantLock岂但提供了 synchronized 对锁的操作性能,还提供了诸如可响应中断锁、可轮询锁申请、定时锁等防止多线程死锁的办法。

ReentrantLock 办法阐明

  • ReentrantLock() : 无参 ReentrantLock 应用的非偏心锁。
  • ReentrantLock(boolean fair):ReentrantLock 能够初始化设置是偏心锁锁,还是非偏心锁。
  • getHoldCount():查问以后线程在某个 Lock上的数量,如果以后线程胜利获取了 Lock,那么该值大于等于 1;如果没有获取到 Lock 的线程调用该办法,则返回值为 0。
  • isHeldByCurrentThread():判断以后线程是否持有某个 Lock,因为 Lock 的排他性,因而在某个时刻只有一个线程调用该办法返回 true。
  • isLocked():判断 Lock 是否曾经被线程持有。
  • isFair():创立的 ReentrantLock 是否为偏心锁。
  • hasQueuedThreads():在多个线程试图获取 Lock 的时候,只有一个线程可能失常取得,其余线程可能(如果应用 tryLock()办法失败则不会进入阻塞)会进入阻塞,该办法的作用就是查问是否有线程正在期待获取锁。
  • hasQueuedThread(Thread thread):在期待获取锁的线程中是否蕴含某个指定的线程。
  • getQueueLength():返回以后有多少个线程正在期待获取锁。

伪代码回顾

精彩片段 1:

class X {private final ReentrantLock lock = new ReentrantLock();

 
    public void m() {
        // 加锁
      lock.lock();  
      try {// 业务执行} finally {
         // 开释锁
        lock.unlock()}
    }

}

精彩片段 2:

class X {private final ReentrantLock lock = new ReentrantLock();

    public void m() {
        // 尝试获取锁
        if (lock.tryLock()) {
            try {// 解决工作 .......} catch (Exception ex) { } finally {
                // 开释锁
                lock.unlock();}
        } else {
            //else 示意没有获取锁 无需敞开
            // ..... 依据理论业务解决(返回、解决其它逻辑)}
    }

}

lock.tryLock() : 阻塞式获取锁,如果可能获取则返回 true 否则 返回 false。无奈获取也能够依据理论业务进行解决。

案例上手

synchronized 案例

public class LockExample1 {

    // 申请总数
    public static int requestTotal = 10000;

    // 并发计数
    public static int count = 0;

    public static void main(String[] args) throws Exception {ExecutorService executorService = Executors.newCachedThreadPool();
        final CountDownLatch countDownLatch = new CountDownLatch(requestTotal);
        for (int i = 0; i < requestTotal; i++) {executorService.execute(() -> {
                try {add();
                } catch (Exception e) { }
                countDownLatch.countDown();});
        }
        countDownLatch.await();
        executorService.shutdown();
        System.out.println("count =" + count);
    }

    private static synchronized void add() {count++;}
}
// 运行后果:count = 10000

add() 办法加上了 synchronized 锁,保障了该办法在并发下也是同步的。

lock() 办法的应用

public class LockExample2 {

    // 申请总数
    public static int requestTotal = 10000;

    // 并发计数
    public static int count = 0;

    private final static Lock lock = new ReentrantLock();

    public static void main(String[] args) throws Exception {ExecutorService executorService = Executors.newCachedThreadPool();
        final CountDownLatch countDownLatch = new CountDownLatch(requestTotal);
        for (int i = 0; i < requestTotal; i++) {executorService.execute(() -> {
                try {add();
                } catch (Exception e) { }
                countDownLatch.countDown();});
        }
        countDownLatch.await();
        executorService.shutdown();
        System.out.println("count =" + count);
    }

    
    private static  void add() {lock.lock();
        try {count++;} finally {lock.unlock();
        }
    }
}
// 运行后果:count = 10000

将须要同步的代码放在 lock 和 unlock 之间,应用 lock 肯定要记得开释锁。

tryLock() 办法

private static void add() {if (lock.tryLock()) {
            try {count++;} finally {
                // 当获取锁胜利时最初肯定要记住 finally 去敞开锁
                lock.unlock();   // 开释锁}
        } else {
            //else 时为未获取锁,则无需去敞开锁
            // 如果不能获取锁,则间接做其余事件
            System.out.println(Thread.currentThread().getName() + "没有获取锁");
        }
    }

通过 tryLock() 办法就发现在并发的状况下会有局部线程无奈获取到锁。

tryLock(long timeout, TimeUnit unit) 能够设置超时工夫

    private static void add() throws InterruptedException {if (lock.tryLock(1000, TimeUnit.MILLISECONDS)) {
            try {count++;} finally {
                // 当获取锁胜利时最初肯定要记住 finally 去敞开锁
                lock.unlock();   // 开释锁}
        } else {
            //else 时为未获取锁,则无需去敞开锁
            // 如果不能获取锁,则间接做其余事件
            System.out.println(Thread.currentThread().getName() + "没有获取锁");
        }
    }

ReentrantLock 提供了偏心和非偏心锁的实现

  • 偏心锁:ReentrantLock lock = new ReentrantLock(true)。是指多个线程在期待同一个锁时,必须依照申请锁的工夫程序来顺次取得锁。
  • 非偏心锁:ReentrantLock lock = new ReentrantLock(false)。如果构造函数不传递参数,则默认是非偏心锁。

欢送关注公众号 山间木匠, 我是小春哥,从事 Java 后端开发,会一点前端、通过继续输入系列技术文章以文会友,如果本文能为您提供帮忙,欢送大家关注、点赞、分享反对,_咱们下期再见!_

正文完
 0