突击并发编程JUC系列-ReentrantLock

突击并发编程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

将需要同步的代码放在 lockunlock 之间,使用 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 后端开发,会一点前端、通过持续输出系列技术文章以文会友,如果本文能为您提供帮助,欢迎大家关注、 点赞、分享支持,我们下期再见!<br />

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342