高效并发JUC锁-砖石

时间:2021-07-31 01:54:49

JUC包的锁(可重入锁和读写锁)

Lock是JAVA5增加的内容,在JUC(java.util.concurrent.locks)包下面,作者是并发大师Doug Lea。JUC包提供了很多封装的锁,包括常用的ReentrantLock和ReadWriteLock。这些所其实都是依赖java.util.concurrent.AbstractQueuedSynchronizer(AQS)这个类来实现的,这个类有个简写的名字叫AQS,对这就是著名的AQS。

关于Lock,先说说线程获取Lock锁的时候会引起哪些事件呢。首先AQS是依赖一个被volatile修饰的int变量来标识当前锁的状态的,为0的时候代表当前锁不被任何线程拥有,当线程拿到这个锁的时候会通过CAS操作修改state的状态,那么对于争用失败的线程AQS会怎么办呢,AQS内部维护了一个等待队列,这个队列是纯JAVA实现的,其实现也是非常巧妙的,多线程在通过CAS来获取自己在队列中的位置,同时队列中的线程状态也是阻塞状态,遇到阻塞就头疼了,上面已经介绍过阻塞会带来的性能问题。在源码中我们可以看到的是AQS通过LockSupport(LockSupport底层依赖Unsafe)将线程阻塞,关于LockSupport其功能是用来代替wait和notity/notifyall的,更好的地方是LockSupport对park方法和unpark方法的调用没有先后的限制,而notify/notifyall必须在wait调用之后调用。尽管如此,这一切并没有阻止线程进入阻塞状态。

锁原理

可重入锁就是当前持有该锁的线程能够多次获取该锁,无需等待。基于AQS实现,AQS是JDK1.5提供的一个基于FIFO等待队列实现的一个用于实现同步器的基础框架,这个基础框架的重要性可以这么说,JUC包里面几乎所有的有关锁、多线程并发以及线程同步器等重要组件的实现都是基于AQS这个框架。AQS的核心思想是基于volatile int state这样的一个属性同时配合Unsafe工具对其原子性的操作来实现对当前锁的状态进行修改。当state的值为0的时候,标识改Lock不被任何线程所占有。

作为AQS的核心实现的一部分,举个例子,我们假设目前有三个线程Thread1、Thread2、Thread3同时去竞争锁,如果结果是Thread1获取了锁,Thread2和Thread3进入了等待队列,AQS的等待队列基于一个双向链表实现的,HEAD节点不关联线程,后面两个节点分别关联Thread2和Thread3,他们将会按照先后顺序被串联在这个队列上。这个时候如果后面再有线程进来的话将会被当做队列的TAIL。当三个线程同时进来,他们会首先会通过CAS去修改state的状态,如果修改成功,那么竞争成功,因此这个时候三个线程只有一个CAS成功,其他两个线程失败,也就是tryAcquire返回false。接下来,addWaiter会把将当前线程关联的EXCLUSIVE类型的节点入队列如果队尾节点不为null,则说明队列中已经有线程在等待了,那么直接入队尾。如果Thread2和Thread3同时进入了enq,同时t==null,则进行CAS操作对队列进行初始化,这个时候只有一个线程能够成功,然后他们继续进入循环,第二次都进入了else代码块,这个时候又要进行CAS操作,将自己放在队尾,因此这个时候又是只有一个线程成功,我们假设是Thread2成功,哈哈,Thread2开心的返回了,Thread3失落的再进行下一次的循环,最终入队列成功,返回自己。当有多个线程,或者说很多很多的线程同时执行的时候,怎么能保证最终他们都能够乖乖的入队列而不会出现并发问题的呢?这也是这部分代码的经典之处,多线程竞争,热点、单点在队列尾部,多个线程都通过【CAS+死循环】这个free-lock黄金搭档来对队列进行修改,每次能够保证只有一个成功,如果失败下次重试,如果是N个线程,那么每个线程最多loop N次,最终都能够成功。上面只是addWaiter的实现部分,那么节点入队列之后会继续发生什么呢,如果Thread1死死的握住锁不放,那么Thread2和Thread3现在的状态就是挂起状态啦,而且HEAD,以及Thread的waitStatus都是SIGNAL,尽管他们在整个过程中曾经数次去尝试获取锁,但是都失败了,失败了不能死循环呀,所以就被挂起了。

锁释放-等待线程唤起首先,Thread1会修改AQS的state状态,加入之前是1,则变为0,注意这个时候对于非公平锁来说是个很好的插入机会,举个例子,如果锁是公平锁,这个时候来了Thread4,那么这个锁将会被Thread4抢去。

我们继续走常规路线来分析,当Thread1修改完状态了,判断队列是否为null,以及队头的waitStatus是否为0,如果waitStatus为0,说明队列无等待线程,按照我们的例子来说,队头的waitStatus为SIGNAL=-1,因此这个时候要通知队列的等待线程,可以来拿锁啦,这也是unparkSuccessor做的事情,unparkSuccessor主要做三件事情:

将队头的waitStatus设置为0.

通过从队列尾部向队列头部移动,找到最后一个waitStatus<=0的那个节点,也就是离队头最近的没有被cancelled的那个节点,队头这个时候指向这个节点。

将这个节点唤醒,其实这个时候Thread1已经出队列了。

还记得线程在哪里挂起的么,上面说过了,在acquireQueued里面,我没有贴代码,自己去看哦。这里我们也大概能理解AQS的这个队列为什么叫FIFO队列了,因此每次唤醒仅仅唤醒队头等待线程,让队头等待线程先出。当有多个线程去竞争同一个锁的时候,假设锁被某个线程占用,那么如果有成千上万个线程在等待锁,有一种做法是同时唤醒这成千上万个线程去去竞争锁,这个时候就发生了羊群效应,海量的竞争必然造成资源的剧增和浪费,因此终究只能有一个线程竞争成功,其他线程还是要老老实实的回去等待。AQS的FIFO的等待队列给解决在锁竞争方面的羊群效应问题提供了一个思路:保持一个FIFO队列,队列每个节点只关心其前一个节点的状态,线程唤醒也只唤醒队头等待线程。

Lock是一个接口,定义了锁的基本方法:

public interface Lock {
void lock();//加锁
void lockInterruptibly() throws InterruptedException;//加可中断锁
boolean tryLock();//加锁成功返回true,失败返回false
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;//加锁成功返回true,失败等待一段时间后若仍无法加锁则返回false,可响应中断
void unlock();//解锁
Condition newCondition(); //返回一个Condition,利用它可以如和 Synchronizd配合使用的wait()和notify()一样对线程阻塞和唤醒,不同的是一个lock可以有多个condition
}

最后的newCondition返回一个Condition对象,该对象是一个接口,我们来看看其中提供的方法。

public interface Condition {
void await() throws InterruptedException;//类似于wait(),可以响应中断
void awaitUninterruptibly();//不响应中断的等待
long awaitNanos(long nanosTimeout) throws
InterruptedException;//等待指定时间(单位是纳秒),在接到信号、被中断或到达指定等待时间之前一直处于等待状态。方法返回被唤醒后的剩余等待时间,若返回值小于等于0则代表此次等待超时。
boolean await(long time, TimeUnit unit) throws
InterruptedException;//指定时间到达前结束等待返回true,否则返回false
boolean awaitUntil(Date deadline) throws InterruptedException;//指定日期到达前被唤醒返回true,否则返回false
void signal();//唤醒一个等待中的线程,类似于notify()
void signalAll();//唤醒所有等待中的线程,类似于notifyAll()
}

可重入锁是Lock接口的一个重要实现类。所谓可重入锁即线程在执行某个方法时已经持有了这个锁,那么线程在执行另一个方法时也持有该锁。

ReadWriteLock是一个接口,也是定义锁的基本方法

public interface ReadWriteLock {
Lock readLock();
Lock writeLock();
}

ReentrantReadWriteLock和ReentrantLock不同,ReentrantReadWriteLock实现的是ReadWriteLock接口

读写锁是接口ReadWriteLock的一个重要实现类。加读锁时其他线程可以进行读操作但不可进行写操作,加写锁时其他线程读写操作都不可进行。

待续。。

高效并发JUC锁-砖石的更多相关文章

  1. 【深入理解JAVA虚拟机】第5部分&period;高效并发&period;2&period;线程安全和锁优化

    1 概述 对于这部分的主题“高效并发”来讲,首先需要保证并发的正确性,然后在此基础上实现高效. 2 线程安全 <Java Concurrency In Practice> 的作者Brian ...

  2. Java多线程系列--&OpenCurlyDoubleQuote;JUC锁”03之 公平锁&lpar;一&rpar;

    概要 本章对“公平锁”的获取锁机制进行介绍(本文的公平锁指的是互斥锁的公平锁),内容包括:基本概念ReentrantLock数据结构参考代码获取公平锁(基于JDK1.7.0_40)一. tryAcqu ...

  3. JUC锁机制

    JUC锁机制(Lock)学习笔记,附详细源码解析 JUC锁机制(Lock)学习笔记,附详细源码解析 2013-08-22 20:03 by CM4J, 56 阅读, 0 评论,收藏, 编辑 锁机制学习 ...

  4. JDK1&period;8 LongAdder 空间换时间&colon; 比AtomicLong还高效的无锁实现

    我们知道,AtomicLong的实现方式是内部有个value 变量,当多线程并发自增,自减时,均通过CAS 指令从机器指令级别操作保证并发的原子性. // setup to use Unsafe.co ...

  5. c&plus;&plus; 高效并发编程

    高效并发编程 并发编程的基本模型包括,通过消息机制来管理运行顺序的message passing, 通过互斥保护共享的shared memory. 线程同步的基本原则 最低限度共享变量,考虑使用imm ...

  6. Java并发 - &lpar;无锁&rpar;篇6

    , 摘录自葛一鸣与郭超的 [Java高并发程序设计]. 本文主要介绍了死锁的概念与一些相关的基础类, 摘录自葛一鸣与郭超的 [Java高并发程序设计]. 无锁是一种乐观的策略, 它假设对资源的访问是没 ...

  7. Java多线程并发08——锁在Java中的应用

    前两篇文章中,为各位带来了,锁的类型及锁在Java中的实现.接下来本文将为各位带来锁在Java中的应用相关知识.关注我的公众号「Java面典」了解更多 Java 相关知识点. 锁在Java中主要应用还 ...

  8. Java多线程专题5&colon; JUC&comma; 锁

    合集目录 Java多线程专题5: JUC, 锁 什么是可重入锁.公平锁.非公平锁.独占锁.共享锁 可重入锁 ReentrantLock A ReentrantLock is owned by the ...

  9. Java多线程系列--&OpenCurlyDoubleQuote;JUC锁”04之 公平锁&lpar;二&rpar;

    概要 前面一章,我们学习了“公平锁”获取锁的详细流程:这里,我们再来看看“公平锁”释放锁的过程.内容包括:参考代码释放公平锁(基于JDK1.7.0_40) “公平锁”的获取过程请参考“Java多线程系 ...

随机推荐

  1. 五、jquery使用工具函数

    工具函数对应的网址在 http://api.jquery.com/categouy/utilities/   工具函数处理对象的不同,可以将其分为几大类别:浏览器的检测.数组和对象的操作.字符串的操作 ...

  2. ASP&period;NET MVC程序中动态修改form的Action值

    在练习ASP.NET MVC时,为了实现一个小功能,POST数据至服务器执行时,需要动态修改form的action值. 下面Insus.NET列举一个例子来演示它.让它简单,明白易了解. 你可以在控制 ...

  3. AIDL Service Android进程间通信机制

    转载出处:http://www.apkbus.com/home.php?mod=space&do=blog&uid=664680&id=59465 我们知道,在Android ...

  4. 【BZOJ】【3093】【FDU校赛2012】A Famous Game

    概率论 神题不会捉啊……挖个坑先 orz 贾教 & QuarterGeek /********************************************************* ...

  5. pl&sol;sql 中F8执行单行sql

    pl/sql中设置: tools->preferences->sql window->AutoSelect statement

  6. log4j2&period;yml配置文件

    # https://blog.csdn.net/u010598111/article/details/80556437 # 共有8个级别,按照从低到高为:ALL < TRACE < DEB ...

  7. python bytes&sol;str

    http://eli.thegreenplace.net/2012/01/30/the-bytesstr-dichotomy-in-python-3/

  8. 自动化测试-19&period;selenium定位之通过JS修改html写入日期数据以及从文本读取数据实战

    # -*- coding: utf-8 -*- from selenium import webdriver from selenium.webdriver.support.select import ...

  9. html生成缩略图来预览解决方案

    html生成缩略图来预览解决方案 一.总结 一句话总结:先将html转化为canvas,然后将canvas生成图片ajax上传到服务器,就可以了 html 转化 canvas 图片 上传 html2c ...

  10. Git单人本地仓库操作

    本地仓库是个.git隐藏文件 以下为演示Git单人本地仓库操作 1.安装git sudo apt-get install git 密码:skylark 2.查看git安装结果 git 3.创建项目 在 ...