前言

本篇博客是《Java锁深入理解》系列博客的第五篇,建议依次阅读。
各篇博客链接如下:
Java锁深入理解1——概述及总结
Java锁深入理解2——ReentrantLock
Java锁深入理解3——synchronized
Java锁深入理解4——ReentrantLock VS synchronized
Java锁深入理解5——共享锁

概述

前面无论是ReentrantLock 还是 synchronized,我们都称之为“独占锁”。其实AQS还支持另外一种锁:共享锁。
说到共享锁。我首先想到的就是“读写锁”里的“读锁”,在我的认知里,共享锁就是读锁。其实也差不多,因为读写锁中动读锁就实现了AQS共享锁机制。JDK中除了读写锁ReentrantReadWriteLock之外,还有CountDownLatch。
共享锁的朴素概念:有多个持用该锁的线程可以同时运行。

其实分析了前面的独占锁代表ReentrantLock,然后又分析了两种共享锁之后。就可以发现AQS的核心就是一个volatile标志(state)和一个同步队列。
同步机制都是:

  • 根据state判断,能抢就抢,不能抢就入同步队列。
  • 入了队列就进入了一个死循环。要么直接阻塞,等着前面的节点来唤醒。唤醒之后就尝试CAS抢锁。
  • 抢到锁才可以结束死循环。

知道了这个规律,我们简单看一下两种共享锁在这个规律基础上有哪些个性化的东西。

ReentrantReadWriteLock

由于它也是可重入锁,而且还分两种锁:读锁,写锁。
想一想ReentrantLock中state的含义:记录有没有线程占用;如果占用了,是占用了几次(重入了几次)。

但对于读锁,这就产生了一个问题:state记录不了这么多含义。尤其是读锁,还允许一堆线程同时拥有它,那怎么记录重入次数呢。
所以ReentrantReadWriteLock扩展了state的含义,并且增加了ThreadLocal来记录每个线程的重入读锁的次数。

特点1: 改造扩展state功能
把state的int(32位二进制数),分解成了两个16位来用。如图所示

左边(高位)用来表示读锁的线程占用数(当前有几个线程在占用读锁)。
右边16位表示写锁的重入数量(因为写锁就是普通排他锁,一次只会有一个线程进来,所以它记录的含义和ReentrantLock一样,记录重入次数)

所以代码中会看到一些“位运算”和“与运算”。

为了记录读锁的重入次数,还用了ThreadLocal,存储在下面这个类的对象

        static final class HoldCounter {int count = 0;// Use id, not reference, to avoid garbage retentionfinal long tid = getThreadId(Thread.currentThread());}

对象中两个成员:

  • tid: 当前线程id
  • 表示该线程的重入读锁的次数

锁降级

对于排他锁,必须一个线程一个线程的处理。
但如果是读写锁,就有点不一样了。想象这样两个场景:

场景1:
线程1抢到了写锁,还在执行过程中。。。
此时有一个线程2,想要抢读锁
按照规则,写锁和读锁是互斥的,所以线程2要等待线程1释放写锁。然后它才能抢读锁。 这个很好理解。

场景2:
线程1抢到了写锁,还在执行过程中。。。
此时线程1 还想要抢读锁
我们给不给它呢?

场景2中,虽然严格按照朴素的读写规则,应该公事公办,不给。
但问题是:同一个线程,它要么读 要么写,肯定不会给自己带来不一致的问题。是不是应该包容这种情况呢?

是的,我们包容了。这就是锁降级。

反过来想

场景3:
线程1抢到了读锁,还在执行过程中。。。
线程1 现在还想抢写锁
我们给不给它呢?

不行。
假如我们给了写锁。它自己读写确实不矛盾,不会产生不一致。但问题是它一开始拿的是读锁。同一时期还有很多线程可能都拿到了读锁。
此时我们给线程1写锁,是不是就跟那其他读锁冲突了。

打个比喻
读锁就像普通游乐园票,很多人都可以一起用。
写锁就像是“包场票”,只能独享。
如果一个线程已经买了“包场票”,再给他一个普通票,当然没问题了。
但如果它只买了普通票,肯定不能再给他“包场票”了。否则游乐园里的其他人怎么办,赶出去吗?

这就是为什么读写锁,只有“锁降级”(写锁变读锁),没有“锁升级”(读锁变写锁)。

CountDownLatch

这个同步器的功能是:定义一个计数器。多个地方都可以减计数器的值。当计数器减到0的时候。阻塞在等待队列里的线程才能开始运行。这也是一个共享锁,因为当计数器减到0,等待队列里的线程允许一起运行。

对这个锁来说,它只关心计数器是不是变成0了,只要变成0,就能运行。
所以state的设计就很简单了,0或非0,作为阻塞判断。

共享锁队列共同的特点

共享锁相对于排他锁,对AQS的队列的使用方式是不太一样的。

  • 排他锁,线程是一个一个执行。后一个线程在前一个线程释放锁之后才会被唤醒。
  • 共享锁,线程可以一起运行。只要前一个线程修改完锁标记等一系列处理之后,不用等开始运行用户程序,更不用等释放锁unlock,就会唤醒下一个节点。
    那unlock的时候干了些什么呢?其实相当于又重复的唤醒了一次。然后主要是修改读些锁自己的锁记录信息(state拆开后,记录的读锁占用线程数,写锁重入数量这些东西)。

这里可能产生一个困惑,既然共享锁节点从队列里出来也是一个一个排队出来,那么他们运行结果是不是顺序执行呢?
答案:是也不是。画个图表示一下

Demo

public class TestReadAndWriteLock {/*** 阅读* @param lock         锁* @param readerName   读者名字*/public void read(Lock lock, String readerName) {lock.lock();try {System.out.println(readerName + " read start");TimeUnit.SECONDS.sleep(1);System.out.println(readerName + " read over");} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}/*** 写* @param lock           锁* @param writerName   作者名称*/public void write(Lock lock, String writerName) {lock.lock();try {System.out.println(writerName + " write start");TimeUnit.SECONDS.sleep(1);System.out.println(writerName + " write over");} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}public static void main(String[] args) {TestReadAndWriteLock tw = new TestReadAndWriteLock();ReadWriteLock rw = new ReentrantReadWriteLock();//定义一个读写锁生成器Lock readLock = rw.readLock();//生成读锁Lock writeLock = rw.writeLock();//生成写锁Lock lock = new ReentrantLock();for (int i = 0; i < 3; i++) {//3个作者入场final int id = i;new Thread(() -> tw.write(writeLock, "作者"+id)).start();}for (int i = 0; i < 3; i++) {//10个读者入场final int id = i;new Thread(() -> tw.read(readLock, "读者"+id)).start();}}}

运行结果

作者1 write start
作者1 write over
作者0 write start
作者0 write over
作者2 write start
作者2 write over
读者1 read start
读者2 read start
读者1 read over
读者0 read start
读者2 read over
读者0 read over

可以看到,写锁保证了写完整流程不受干扰。
但读锁之间,会相互穿插。虽然他们是顺序从队列里出来,但用户程序部分基本是并行的

小结

  • ReentrantReadWriteLock和CountDownLatch作为两种AQS共享锁的实现。
    依然沿用了AQS的主体框架:判断标志state和同步队列
  • 他们都根据自己要实现的同步效果,都对state含义上做了扩展改造(int字段没变,只是解释的意思变了。尤其是ReentrantReadWriteLock,甚至把int拆成了二进制来读取)。
  • 同步队列的入队逻辑基本都一样,只是唤醒节点的时机不太一样。

Java锁深入理解5——共享锁相关推荐

  1. 阿里P8大牛总结的Java锁机制入门笔记,堪称教科书式天花板

    前言 锁机制无处不在,锁机制是实现线程同步的基础,锁机制并不是Java锁独有的,其他各种计算机语言中也有着锁机制相关的实现,数据库中也有锁的相关内容.这篇文章就是从Java入手,深入学习.理解Java ...

  2. java锁(公平锁和非公平锁、可重入锁(又名递归锁)、自旋锁、独占锁(写)/共享锁(读)/互斥锁、读写锁)

    前言 本文对Java的一些锁的概念和实现做个整理,涉及:公平锁和非公平锁.可重入锁(又名递归锁).自旋锁.独占锁(写)/共享锁(读)/互斥锁.读写锁 公平锁和非公平锁 概念 公平锁是指多个线程按照申请 ...

  3. java线程钥匙_Java多线程并发编程/锁的理解

    一.前言 最近项目遇到多线程并发的情景(并发抢单&恢复库存并行),代码在正常情况下运行没有什么问题,在高并发压测下会出现:库存超发/总库存与sku库存对不上等各种问题. 在运用了 限流/加锁等 ...

  4. Java锁详解:“独享锁/共享锁+公平锁/非公平锁+乐观锁/悲观锁+线程锁”

    在Java并发场景中,会涉及到各种各样的锁如公平锁,乐观锁,悲观锁等等,这篇文章介绍各种锁的分类: 公平锁/非公平锁 可重入锁 独享锁/共享锁 乐观锁/悲观锁 分段锁 自旋锁 线程锁 乐观锁 VS 悲 ...

  5. 深入理解 Java 锁与线程阻塞

    相信大家对线程锁和线程阻塞都很了解,无非就是 synchronized, wait/notify 等, 但是你有仔细想过 Java 虚拟机是如何实现锁和阻塞的呢?它们之间又有哪些联系呢?如果感兴趣的话 ...

  6. 最全Java锁详解:独享锁/共享锁+公平锁/非公平锁+乐观锁/悲观锁

    在Java并发场景中,会涉及到各种各样的锁,比如:高并发编程系列:4种常用Java线程锁的特点,性能比较.使用场景,这些锁有对应的种类:公平锁,乐观锁,悲观锁等等,这篇文章来详细介绍各种锁的分类: 公 ...

  7. 使用java理解程序逻辑 第十二章_Java多线程中锁的理解与使用(二)

    博主将会针对Java面试题写一组文章,包括J2ee,SQL,主流Web框架,中间件等面试过程中面试官经常问的问题,欢迎大家关注.一起学习,一起成长. 独享锁/共享锁 独享锁是指该锁一次只能被一个线程所 ...

  8. 深入理解JAVA锁的机制

    2019独角兽企业重金招聘Python工程师标准>>> 1. synchronized实现原理 在java代码中使用synchronized可是使用在代码块和方法中,根据Synchr ...

  9. 【Java面试题】6年开发去A里面试P6竟被Mysql难住了,说一下你对行锁、临键锁、间隙锁的理解

    一个工作了6年的程序员,最近去阿里面试p6这个岗位. 面试之前信心满满的和我说,这次一定要拿下 35k月薪的offer. 然后,在第一面的时候,被Mysql里面的一个问题难住了. 大家好,我是Mic, ...

最新文章

  1. mysql的存储引擎种类,mysql 存储引擎,基本数据类型
  2. php ajax 点击后刷新当前页面,ajax请求值后返回会刷新页面?
  3. JZOJ 5669. 【GDSOI2018模拟4.19】排列
  4. go gin框架:Any响应任何请求类型
  5. QT学习:网络应用开发练习(文件下载)
  6. python里面的正则表达式_Python中的正则表达式
  7. SpringCloud的微服务网关:zuul(理论)
  8. .NET 现代化动态 LINQ 库 Gridify
  9. nodeJs的学习之路(1)
  10. 使用IntelliJ IDEA 2019.3.2 x64 远程连接oracle数据库
  11. Redis学习---(15)Redis 脚本
  12. Linux内核深入理解定时器和时间管理(7):相关的系统调用
  13. 图论:柯尼斯堡桥问题、艾科西亚游戏
  14. 【译】JavaScript面试问题:事件委托和this
  15. java某个参数值设置为空_@PathVariable为空时指定默认值的操作
  16. 淦!这个非科班学妹是真的厉害...
  17. selenium+webdriver+java(基本小例子及初始化三种浏览器)---------------
  18. CIO:权大、钱多、但难干 | 凌云时刻
  19. 四层和八层电梯控制系统Proteus仿真设计,51单片机,附仿真和Keil C代码
  20. yolov3识别的类别_Yolo3 如何只识别一个类别

热门文章

  1. select函数到底该怎么用?
  2. 快手视频搬运快手视频伪原创工具快手视频消重快手去重的软件短视频消重批量处理软件,短视频伪原创...
  3. 第三代电力电子半导体:SiC MOSFET学习笔记(五)驱动电源调研
  4. python写BMI计算器
  5. 深度学习中number of training epochs中的,epoc h到底指什么?
  6. cf596B. Wilbur and Array
  7. 教你分析快递揽收后,第二条物流是否超过12小时
  8. 信息安全中常见的网络知识(一)网络基本概念
  9. 自行车小组问卷调查报告
  10. 电脑开机无限重启,到了欢迎界面就黑屏重启