3.2 StampedLock

该类自JDK8加入,是为了进一步优化读性能,它的特点是使用读锁、写锁时都必须配合【戳】使用
加解读锁

加锁写锁

乐观锁,StampedLock 支持 tryOptimisticRead()方法(乐观读),读取完毕后需要做一次戳校验,如果校验通过,表示这期间确实没有写操作,数据可以安全使用,如果校验没通过,需要重新获取读锁,保证数据安全。

4. Semaphore

信号量,用来限制能同时访问共享资源的线程上限。

Semaphore原理

  1. 加锁解锁流程
    Semaphore 有点像一个停车场,permits 就好像停车位数量,当线程获得了 permits就像是获得了停车位,然后停车场显示空余车位减一
    刚开始,permits(state)为3,这时5个线程来获取资源

    假设其中 Thread - 1,Thread - 2,Thread -4 cas 竞争成功,而Thread - 0和Thread - 3 竞争失败,进入AQS队列park阻塞

    这时 Thread - 4 释放了 permits,状态如下

    接下来Thread - 0 竞争成功,permits再次设置为0,设置自己为head节点,断开原来的head节点,unpark接下来的Thread - 3 节点,但由于 permits是0,因此Thread - 3 在尝试不成功后再次进入 park状态

5. CountdownLatch

用来进行线程同步协作,等待所有线程完成倒计时。
其中构造参数用来初始化等待计数值,await()用来等待计数归零,countDown()用来让计数减一

6. CyclicBarrier

循环栅栏,用来进行线程协作,等待线程满足某个计数。构造时设置【计数个数】,每个线程执行到某个需要“同步”的时刻调用await()方法进行等待,当等待的线程数满足【计数个数】时,继续执行

7.线程安全集合类概述


线程安全集合类可以分为三大类 :

  • 遗留的线程安全集合如 Hashtable、Vector
  • 使用Collections装饰的线程安全集合,如 :
    • Collections.synchronizedCollection
    • Collections.synchronizedList
    • Collections.synchronizedMap
    • Collections.synchronizedSet
    • Collections.synchronizedNavigableMap
    • Collections.synchronizedNavigableSet
    • Collections.synchronizedMap
    • Collections.synchronizedSet
  • java.util.concurrent.*
    重点介绍java.util.concurrent.*下的线程安全集合类,里面包含三类关键词 :Blocking、CopyOnWrite、Concurrent
  • Blocking大部分实现基于锁,并提供用来阻塞的方法
  • CopyOnWrite 之类容器修改稍相对较重
  • Concurrent类型的容器
    • 内部很多操作使用cas优化,一般可以提供高吞吐量
    • 弱一致性
      • 遍历时弱一致性,例如,当利用迭代器遍历时,如果容器发生修改,迭代器任然可以进行进行遍历,这时内存是旧的
      • 求大小弱一致性,size操作未必是100%准确
      • 读取弱一致性
  • 遍历时如果发生了修改,对于非安全容器来讲,使用fail-fast机制也就是让遍历立刻失败,抛出ConcurrentModificationExecption,不再继续遍历

8. ConcurrentHashMap

单词计数问题 :


重要属性和内部类

构造器分析
可以看到实现了懒惰初始化,在构造方法中仅仅计算了table的大小,以后在第一次使用时才会真正的创建

get流程分析

put流程
以下数组简称(table),链表简称(bin)









size计算流程
size计算实际发生在put、remove改变集合元素的操作之中

  • 没有竞争发生,向baseCount累加计数
  • 有竞争发生,新建counterCells,向其中的一个cell累加计数
    • counterCells初始有两个cell
    • 如果计数竞争比较激烈,会创建新的cell来累加计数

3. JDK7 ConcurrentHashMap

它维护了一个segment数组,每个segment对应一把锁

  • 优点 : 如果多个线程访问不同的segment,实际是没有冲突的,这与jdk8中是类似的
  • 缺点 :Segments数组默认大小为16,这个容量初始化指定后就不能改变了,并且不是懒得初始化
    构造器分析


    可以看到ConcurrentHashMap 没有实现懒惰初始化,空间占用不友好
    其中 this.segmentShift 和 this.segmentMask的作用是决定将key的hash结果匹配到那个 segment
    例如 :根据某一hash值求segment位置,先将高位向低位移动 this.segmentShift位

    结果再与this.segmentMask 做位于运算,最终得到1010即下标为10的segment

    put流程

    segment继承了可重入锁(ReentrantLock),它的put方法为



    get流程
    get时并未加锁,用了UNSAFE方法保证了可见性,扩容过程中,get先发生就从旧表取内容,get后发生就从新表取内容

    size计算流程
  • 计算元素个数前,先不加锁计算两次,如果前后两次结果如一样,认为个数正确返回
  • 如果不一样,进行重试,重试次数超过3,将所有segment锁住,重新计算个数返回



    LinkedBlockingQueue 原理
  1. 基本的入队出队

    初始化链表 last = head = new Node(null);Dummy节点用来占位,item为null

    当一个节点入队 last = last.next = node;

    再来一个节点入队 last = last.next = node

    出队

    h = head

    first = h.next

    h.next = h

    head = first

    E x = first.item;
    first.item = null;
    return x;
  2. 加锁分析
    高明之处在于用了两把锁和dummy节点
  • 用一把锁,同一时刻,最多只允许有一个线程(生产者或消费者,二选一)执行
  • 用两把锁,同一时刻,可以允许两个线程同时(一个生产者与一个消费者)执行
    • 消费者与消费者线程任然串行
    • 生产者与生产者线程任然串行
      线程安全分析
  • 当节点总数大于2时(包括dummy节点),putLock保证的是last节点的线程安全,takeLock保证的是head节点的线程安全。两把锁保证了入队和出队没有竞争
  • 当节点总数等于2时(即一个dummy节点,一个正常节点)这时候,仍然是两把锁锁两个对象,不会竞争
  • 当节点总数等于1时(就一个dummy节点)这时take线程会被notEmpty条件阻塞,有竞争,会阻塞

    put操作

    take操作

3. 性能比较

主要列举 LinkedBlockingQueue与ArrayBlockingQueue的性能比较

  • Linked支持有界,Array强制有界
  • Linked实现是链表,Array实现是数组
  • Linked是懒惰的,而Array需要提前初始化Node数组
  • Linked每次入队会生成新Node,而Array的Node是提前创建好的
  • Linked两把锁,Array一把锁

10. ConcurrentLinkedQueue

ConcurrentLinkedQueue的设计与LinkedBlockingQueue 非常像,也是

  • 两把【锁】,同一时刻,可以允许两个线程同时(一个生产者与一个消费者)执行
  • dummy节点的引入让两把【锁】将来锁住的是不同对象,避免竞争
  • 只是这【锁】使用了cas来实现
    事实上,ConcurrentLinkedQueue应用还是非常广泛的
    例如之前讲的Tomcat的Connector结构时,Acceptor作为生产者向Poller消费者传递事件信息时,正是采用了ConcurrentLinkedQueue将SocketChannel给Poller使用

11. CopyOnWriteArrayList

CopyOnWriteArraySet 是它的马甲
底层实现采用了写入时拷贝的思想,增删改操作会将底层数组拷贝一份,更改操作在新数组上执行,这时不影响其他线程的并发读、读写分离。

这里的源码版本是Java11,在Java1.8中使用的是可重入锁而不是synchronized,其它读操作并未加锁,例如 :

适合【读多写少】的应用场景
get弱一致性


迭代器弱一致性

不要觉得弱一致性就不好

  • 数据库的MVCC都是弱一致性的表现
  • 并发高和一致性是矛盾的,需要权衡

并发编程之J.U.C的第二篇的更多相关文章

  1. 并发编程之J.U.C的第一篇

    并发编程之J.U.C AQS 原理 ReentrantLock 原理 1. 非公平锁实现原理 2)可重入原理 3. 可打断原理 5) 条件变量实现原理 3. 读写锁 3.1 ReentrantRead ...

  2. [转载]并发编程之Operation Queue和GCD

    并发编程之Operation Queue http://www.cocoachina.com/applenews/devnews/2013/1210/7506.html 随着移动设备的更新换代,移动设 ...

  3. 并发编程之 Exchanger 源码分析

    前言 JUC 包中除了 CountDownLatch, CyclicBarrier, Semaphore, 还有一个重要的工具,只不过相对而言使用的不多,什么呢? Exchange -- 交换器.用于 ...

  4. Java并发编程之CAS二源码追根溯源

    Java并发编程之CAS二源码追根溯源 在上一篇文章中,我们知道了什么是CAS以及CAS的执行流程,在本篇文章中,我们将跟着源码一步一步的查看CAS最底层实现原理. 本篇是<凯哥(凯哥Java: ...

  5. Java并发编程之CAS第三篇-CAS的缺点及解决办法

    Java并发编程之CAS第三篇-CAS的缺点 通过前两篇的文章介绍,我们知道了CAS是什么以及查看源码了解CAS原理.那么在多线程并发环境中,的缺点是什么呢?这篇文章我们就来讨论讨论 本篇是<凯 ...

  6. Java并发编程之set集合的线程安全类你知道吗

    Java并发编程之-set集合的线程安全类 Java中set集合怎么保证线程安全,这种方式你知道吗? 在Java中set集合是 本篇是<凯哥(凯哥Java:kagejava)并发编程学习> ...

  7. 并发编程之ThreadLocal

    并发编程之ThreadLocal 前言 当多线程访问共享可变数据时,涉及到线程间同步的问题,并不是所有时候,都要用到共享数据,所以就需要线程封闭出场了. 数据都被封闭在各自的线程之中,就不需要同步,这 ...

  8. 并发编程之:Atomic

    大家好,我是小黑,一个在互联网苟且偷生的农民工. 在开始讲今天的内容之前,先问一个问题,使用int类型做加减操作是不是线程安全的呢?比如 i++ ,++i,i=i+1这样的操作在并发情况下是否会有问题 ...

  9. Java并发编程之CAS

    CAS(Compare and swap)比较和替换是设计并发算法时用到的一种技术.简单来说,比较和替换是使用一个期望值和一个变量的当前值进行比较,如果当前变量的值与我们期望的值相等,就使用一个新值替 ...

随机推荐

  1. Java8 Stream用法详解

    1.概述 Stream 的原理:将要处理的元素看做一种流,流在管道中传输,并且可以在管道的节点上处理,包括过滤筛选.去重.排序.聚合等.元素流在管道中经过中间操作的处理,最后由最终操作得到前面处理的结 ...

  2. 微软CEO:我们员工以及他们家人的健康与安全是我们最高的优先级!

    在肺炎疫情开始之初,微软中国各个部门就立即采取了各种的防控和关怀措施. 在昨天,微软 CEO Satya Nadella 给微软中国全体员工写了一封邮件. 其中,最让我们感动的就是这句话:" ...

  3. Docker底层架构之简介

    简介 Docker 底层的核心技术包括 Linux 上的命名空间(Namespaces) 控制组(Control groups) Union 文件系统(Union file systems) 容器格式 ...

  4. Web 开发工具类(4): IDUtils

    package com.easybuy.utils; import java.util.Random; /** * * <p>Title: IDUtils</p> * < ...

  5. 六、Django学习之基于下划线的跨表查询

    六.Django学习之基于下划线的跨表查询 一对一 正向查询的例子为 已知用户名,查询用户的电话号码.反向查询例子反之. 正向查询 其中下划线前的表示表名,无下划线的表示的是Author表 resul ...

  6. MybatisDao

    一.mybatisDao的编写(原始方式,不用) 1.接口编写 public interface UserDao { public void save(User user); public User ...

  7. zabbix4.0的安装与配置

    #安装zabbix监控首先的先安装LNMP环境,在这里我采用事先准备好的脚本进行安装LNMP环境 脚本内容如下: #!/bin/bash # DATE:Wed Jan # hw226234@126.c ...

  8. 为什么尽量不要使用Executors创建线程池

    看阿里巴巴开发手册并发编程这块有一条:线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,通过源码分析禁用的原因. 线程池的优点 管理一组工作线程,通过线程池 ...

  9. java8种基本数据类型

  10. Mac-Mysql忘记root密码

    cd /usr/local/mysql/bin 切换到root权限 ,需要输入密码: sudo su 输入之后会看见如下信息: sh-3.2# 使用如下命令以安全模式运行mysql ./mysqld_ ...