Java并发包JUC

JDK核心库rt.jar里的包

  • java.lang.* 最核心的包
  • java.io.*
  • java.util.*
  • java.math.*
  • java.net.*
  • java.rmi.*
  • java.sql.*
  • javax.*
  • sun.*

    JUC

  • java.util.concurrent 并发包,五大模块:

  • 子包
    • atomic 原子类
      • AtomicInteger
    • locks 锁
      • Lock
      • Condition
      • ReadWriteLock
  • 线程池Executor
    • Future
    • Callable
    • Executor
  • 工具类Tools
    • CountDownLatch
    • CyclicBarrier
    • Semaphore
  • 集合类Collections
    • CopyOnWriteArrayList
    • ConcurrentMap

JUC.png

为什么需要显示的Lock

  • synchronized 可以加锁,是个隐式的锁
    • 方法锁
    • 对象锁
    • 代码块锁
  • wait/nofity也可以看作加锁/解锁
  • 原因

    • synchronized 锁不灵活
      • 同步块无法根据条件灵活的加锁解锁,即只能跟同步块范围一致(空间问题)
      • 同步块的阻塞无法控制超时时间,无法自动解锁(时间问题)
      • 同步块无法异步处理锁,即不能立即知道是否可以拿到锁
      • 同步块的阻塞无法中断,后面的线程只能干等,不能主动掉头

        更灵活的锁Lock

  • java.util.concurrent.locks

  • 使用方式灵活可控
    • 根据条件动态调整范围
    • 控制锁的时间
  • 性能开销小

    基础接口

    | 重要方法 | 说明 | | —- | —- | | void lock(); | 获取锁;类比sychronized(lock) | | void lockInterruptibly() throws InterruptedException; | 获取锁;允许打断 | | boolean tryLock(); | 尝试获取锁,无等待,成功返回true | | void unlock(); | 解锁;要求当前线程已获得锁;类比同步块结束 | | Condition newCondition(); | 新增一个绑定到当前Lock的条件;
    示例:(类比:Object monitor)
    final Lock lock = new ReentrantLock();
    final Condition notFull = lock.newCondition();
    final Condition notEmpty = lock.newCondition(); |

可重入锁

  • new ReentrantLock()
    • 可重入锁即某个线程获取到了锁,在未释放锁之前如果再次执行到锁的代码则可直接进入,不必等待
  • 传入true为公平锁
    • 先来排队的线程先获取到锁
  • 传入false为非公平锁

    • 排队中的线程谁先获取到下一把锁不确定

      1. public class LockCounter{
      2. private int sum = 0;
      3. private Lock lock = new ReentrantLock(true);
      4. public int addAndGet(){
      5. try{
      6. lock.lock();
      7. return ++sum;
      8. }finally{
      9. lock.unlock();
      10. }
      11. }
      12. public int getSum(){
      13. return sum;
      14. }
      15. }

      读写锁

      | 重要方法 | 说明 | | —- | —- | | Lock readLock(); | 获取读锁;共享锁 | | Lock writeLock(); | 获取写锁;独占锁(也排斥读锁) |

  1. // 构造方法
  2. public ReentrantReadWriteLock(boolean fair){
  3. sync = fair ? new FairSync() : new NonfairSync();
  4. readerLock = new ReadLock(this);
  5. writerLock = new WriterLock(this);
  6. }
  7. public class ReadWriteLockCounter{
  8. private int sum = 0;
  9. // 可重入 - 读写锁 - 公平锁
  10. private ReadWriteLock lock = new ReentrantReadWriteLock(true);
  11. public int incrAndGet(){
  12. try{
  13. lock.writeLock().lock(); // 写锁
  14. return ++sum;
  15. }finally{
  16. lock.writeLock().unlock();
  17. }
  18. }
  19. public int getSum(){
  20. try{
  21. lock.readLock().lock(); // 读锁
  22. return ++sum;
  23. }finally{
  24. lock.readLock().unlock();
  25. }
  26. }
  27. }

锁优化

  • 锁粒度:减小锁的范围
  • 锁分离:把一把锁分离成N把,专门处理自己的范围

    Condition

  • wait/notify/notifyAll是作用在Object

  • await/signal/signalAll是作用在Lock | 重要方法 | 说明 | | —- | —- | | void await() throws InterruptedException | 等待信号;类比Object#wait() | | void awaitUninterruptibly(); | 等待信号; | | boolean await(long time, TimeUnit unit) throws | 等待信号;超时则返回false | | boolean awaitUntil(Date deadline) throws | 等待信号;超时则返回false | | void signal(); | 给一个等待线程发送唤醒信号;类比Object#notify() | | void signalAll(); | 给所有等待线程发送唤醒信号;类比Object#notifyAll(); |

LockSupport

  • 锁当前线程
  • LockSupport.png

    用锁最佳实践

  • 永远只在更新对象的成员变量时加锁

  • 永远只在访问可变的成员变量时加锁
  • 永远不在调用其他对象的方法时加锁

    并发原子类

    Atomic工具类

  • 原子类工具包java.util.concurrent.atomic

  • Atomic.png

    1. public class AtomicCounter{
    2. private AtomicInteger sum = new AtomicInteger(0);
    3. public int incrAndGet(){
    4. return sum.incrementAndGet();
    5. }
    6. public int getSum(){
    7. return sum.get();
    8. }
    9. }

    无锁技术

  • 底层原理

    • UnsafeAPI-Compare-And-Swap
    • CPU硬件指令支持:CAS指令
    • CAS指令作为乐观锁实现,通过自旋重试保证写入
  • Atomic实现
    • volatile保证主内存可见
    • CAS保证原子性

volatile.png
UnsafeCAS.png

CAS

  • 乐观锁/自旋锁
  • 举例

    • 更新数据库中余额,原本5,消费3
    • 采用悲观锁就是 select for update
    • 采用乐观锁就是 5 - 3 = 2 在更新回数据库时再去获取下当前余额如果是5就正常更新,如果不是5就自旋回来继续计算一遍,直到更新成功,就是CAS

      LongAdder对AtomicLong的改进

  • AtomicInteger和AtomicLong里的value是所有线程竞争读写的热点数据;

  • 将单个value拆分成跟线程一样多的数组Cell[]
  • 每个线程写自己的Cell[i]++,最后对数组求和
  • 多路归并的思想

    • 快排
    • G1GC
    • ConcurrentHashMap

      锁与无锁之争

  • 在并发比较低时

    • 无锁简单,效率高
  • 在并发稍高时
    • 无锁因为锁的范围最小,效率稍高
  • 在并发非常高时

    • 无锁可能在重复的自旋,可能会产生问题,这时就推荐使用显示锁

      并发工具类

      多线程相互协作

  • 简单机制

    • wait/notify
    • Lock/Condition
  • 复杂场景

    • 控制实际并发量
    • 多个线程某个时间同时开始
    • 指定数量线程达到某个状态再继续

      AQS

  • AbstractQueuedSynchronizer,抽象队列同步器

  • 是构建锁或者其他同步组件的基础,是JUC包中的核心基础组件
    • Semapheore
    • CountDownLatch
    • ReentrantLock
    • ReentrantReadWriteLock
  • 两种资源共享方式,子类负责实现公平OR非公平
    • 独占
    • 共享

AQS.png

Semaphore

  • 信号量
  • 准入数量N,N=1则等价于独占锁
  • 使用场景:同一时间控制并发线程数

    CountdownLatch

  • 使用场景:Master线程等待Worker线程把任务执行完

  • CountdownLatch.png
  • CountdownLatch示例.png

    CyclicBarrier

  • 场景:任务执行到一定阶段,等待其他任务对齐

  • 示例:等待所有人都到达,再一起开吃

CyclicBarrier.png
CyclicBarrier.png

对比

比较.png
对比.png

Future/FutureTask/CompletableFuture

Future.png
CompletableFuture.png

常用线程安全类型

JDK基础数据类型与集合类

  • List
    • ArrayList
    • LinkedList
    • Vector
    • Stack
  • Set
    • LinkedSet
    • HashSet
    • TreeSet
  • Queue
    • Deque
    • LinkedList
  • Map
    • HashMap
    • LinkedHashMap
    • TreeMap
  • Dictionary

    • HashTable
    • Properties

      ArrayList

  • 基本特点

    • 基于数组,便于按index访问
    • 超过数组需要扩容,成本比较高
  • 原理
    • 使用数组模拟列表,默认大小10
    • 扩容X1.5
  • 安全问题

    • 写冲突:两个写,相互操作冲突
    • 读写冲突:
      • 读:特别是iterator的时候,数据个数变了,拿到了非预期的数据或者报错
      • 产生ConcurrentModificationException

        LinkedList

  • 基本特点

    • 使用链表实现,无需扩容
    • 不知道容量,插入多的情况
  • 原理
    • 使用双向指针将所有节点连起来
  • 安全问题

    • 写冲突:两个写,相互操作冲突
    • 读写冲突:
      • 读:特别是iterator的时候,数据个数变了,拿到了非预期的数据或者报错
      • 产生ConcurrentModificationException

        List线程安全的最简单办法

  • 既然线程安全是写冲突和读写冲突导致的

  • 最简单的办法就是读写都加锁
  • 例如

    • ArrayList的方法都加上synchronized->Vector,但这个没有解决读写锁冲突的问题
    • Collections.synchronizedList,强制将List操作加上同步
    • Arrays.asList 不允许添加删除,但是可以set替换
    • Collections.unmodifiableList 不允许修改内容,包括添加删除和set

      CopyOnWriteArrayList

  • 核心改进原理

    • 写加锁,保证写不会混乱
    • 写在一个copy副本上,而不是原始数据
  • 特点
    • 读写分离
    • 最终一致

CopyOnWriteArrayList.png

HashMap

  • 基本特点
    • 空间换时间
    • 哈希冲突不大的情况下查找数据性能很高
  • 用途
    • 存放指定key的对象
    • 缓冲对象
  • 原理
    • 使用hash原理,存kv数据
    • 初始容量16,扩容X2
    • 负载因子0.75
  • JDK8以后,在链表长度到8&数组长度到64时,使用红黑树

HashMap.png

  • 安全问题

    • 写冲突
    • 读写问题,可能会死循环
    • keys()无序问题

      LinkedHashMap

  • 基本特点

    • 继承自HashMap,对Entry集合添加了一个双向链表
  • 用途
    • 保证有序
    • 特别是Java8 stream操作的toMap时使用
  • 原理
    • 同LinkedList
    • 包括插入顺序和访问顺序
  • 安全问题

    • 同HashMap

      ConcurrentHashMap

  • Java7

    • 引入了segment实现分段锁,理论上最大并发等于 segment个数
    • 默认16个segment(concurrentLevel=16),降低锁粒度
    • 类比Segment[]就是分库 HashEntry[]就是分表
    • ConcurrentHashMap.png
  • Java8

    • 为了进一步提升性能,摒弃了分段锁的方案,而是直接使用一个大的数组
    • Java8CHM.png

      总结

      总结.png

      并发编程相关内容

      线程安全操作利器ThreadLocal

  • 线程本地变量

  • 场景:每个线程一个副本
  • 不改方法签名静默传参
  • 及时进行清理
  • 可以看作是Context模式,减少显式传递参数 | 重要方法 | 说明 | | —- | —- | | public ThreadLocal() | 构造方法 | | protected T initialValue() | 覆写-设置初始默认值 | | void set(T value) | 设置本线程对应的值 | | void remove() | 清理本线程对应的值 | | T get() | 获取本线程对应的值 |

并行Stream

  • 多线程执行,只需要加一个parallel即可
  • 并行Stream.png

    伪并发问题

  • 跟并发冲突问题类似的场景很多

  • 比如浏览器端表单的重复提交
  • 解决

    • 客户端控制,点击后按钮不可用或跳转到其他页面
    • 服务端控制,给每个表单生成一个编号,提交时判断重复

      分布式下的锁和计数器

  • 分布式环境下,多个机器的操作,超出了线程的协作机制,一定是并行的

  • 例如某个任务只能有一个应用处理,部署了多个机器,怎么控制
  • 针对用户的限流是每分钟60次计数,API服务器有3台,用户可能随时访问到任何一台,怎么控制

    并发编程经验总结

    加锁需要考虑的问题

  • 粒度

  • 性能
  • 重入
  • 公平
  • 自旋锁
  • 场景:脱离业务场景谈性能都是耍流氓

    线程间协作与通信

  • 线程间共享

    • static/实例变量(堆内存)
    • Lock
    • synchronized
  • 线程间协作

    • Thread#join()
    • Object#wait/notify/notifyAll
    • Future/Callable
    • CountDownLatch
    • CyclicBarrier

      进程间通信

  • 信号量

  • 管道
  • 共享内存
  • 文件
  • socket
  • 自定义网络协议
  • 数据库

    并发编程常见面试题