concurrent并发包入门(一)

java.util.concurrent 简称(JUC) 作者:Doug Lea

1.概念

JUC包含许多线程安全/测试良好/高性能并发构建块

Concurrent可以实现Collection框架对数据结构所执行的兵法操作

通过并发模块,可以提高并发类的线程安全/可伸缩性/性能/可读性/可靠性

2.对比

synchronized通过(monitor)监视器锁来完成同步

每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,获取不到则进入阻塞状态

monitorenter 进入设置(初始为0,同一个线程每次进入+1,其他线程阻塞)

monitorexit 离开设置(当值为0时,表示线程释放锁,所有线程可以再次争夺)

java反编译字节码(同步方法)不显示monitor的两条指令,而是通过ACC_SYNCHRONIZED封装,隐式调用,但仍是执行monitor指令来加解锁

wait/notify 等方法的底层同样是基于监视器,因此只有在同步方法(块)中才能调用

JUC通过ASC/AQS/CLH等同步技术来完成同步

更加灵活,对资源的损耗较少等如上概念所提

3.构成

Atomic : AtomicInteger 原子数据的构建

Locks : Lock, Condition, ReadWriteLock 基本的锁实现,最重要的AQS框架和lockSupport

Collections : Queue, ConcurrentMap 构建一些集合的工具

Executer : Future, Callable, Executor 构建一些线程池的工具

Tools : CountDownLatch, CyclicBarrier, Semaphore 并发队列等

以上都用到了CAS(compare-and-swap)操作.

4.CAS介绍

(compare-and-swap)比较和交换

CAS是一种低级别的/细粒度的技术,它允许多个线程更新一个内存位置,同时能够检测其他线程的冲突并进行恢复,他是许多高性能并发算法的基础.

JDK5.0之前,java语言中用于协调线程之间的访问的唯一原语是同步(更重量级和粗粒度),

公开CAS可以开发高度可伸缩的并发java类,这些更改主要由JDK库类使用,而不是开发人员

CAS操作都封装在java不公开的类库中,sun.misc.Unsafe.

此类包含了对原子操作的封装,具体用本地代码实现,本地的C代码直接利用到了硬件上的原子操作

5.AQS介绍

(abstract-queued-synchronizer)队列同步器

JDK1.6以前,synchronized重量级锁的性能一直都较为低下,虽然在1.6以后进行了大量的锁优化策略,

但与Lock相比,synchronized还是存在一些缺陷

5-1.synchronized优缺

优点:提供了便捷性的隐式获取锁释放锁机制(基于JVM机制)

缺点:缺少了获取锁和释放锁的可操作性,可中断/超时获取锁等,且它为独立式,在高并发场景下性能大打折扣

5-2.AQS

构建锁或其他同步组件的基础框架,如:ReentrantLock/ReentrantReadWriteLock/Semaphore等

  1. JUC并发包的作者希望它能够成为实现大部分同步需求的基础,是JUC并发包中的核心基础组件
  2. AQS解决了实现同步器时设计的大量细节问题,如:获取同步状态/FIFO同步队列
  3. 基于AQS来构同步器可以带来很多好处,不仅能极大地减少实现工作,也不必处理多个位置上发生竞争问题
  4. 基于AQS构建的同步器中,只能在同一时刻发生阻塞,从而降低上下文切换带来的开销,提高吞吐量.
  5. 设计AQS时充分考虑了可伸缩性,JUC中所有基于AQS构建的同步器均可获得这个优势

AQS通过内置的FIFO同步队列(CLH)来完成资源获取线程的排队工作,如果当前线程获取同步状态(锁)失败,

AQS则会将当前线程以及等待状态等信息构建成一个节点(Node)并将其加入到同步队列中,

同时阻塞当前线程,当同步状态释放时,会把节点中的线程唤醒,使其再次尝试获取同步状态

5-3.AQS使用

AQS主要使用方式是集成,子类通过集成同步器并实现它的抽象方法来管理同步状态

AQS使用一个int类型的成员变量state来表示同步状态

  • 当state>0表示获取锁
  • state=0表示释放锁

提供三个方法来对同步状态state进行操作

  1. getState() 获取当前同步状态值;
  2. setState(int newState)设置当前同步状态;
  3. compareAndSetState(int expect,int update) 使用CAS设置当前状态,该方法能够保证状态设置的原子性;

AQS可以确保对state的操作是安全的

AQS主要提供的方法

getState() 获取当前同步状态值

setState() 设置当前同步状态

compareAndSetState(int expect,int update) 使用CAS设置当前状态,该方法能够邦正状态设置的原子性

tryAcquire(int arg) 独占式获取同步状态,获取同步状态成功后,其他线程需要等待该线程释放同步状态才能获取同步状态

tryRelease(int arg) 独占式释放同步状态

  • —-共享式获取同步状态,返回值>=0则表示获取成功,否则获取失败

tryReleaseShared(int arg) 共享式释放同步状态

isHeldExclusively() 当前同步器是否独占式模式下被线程占用,一般该方法表示是否被当前线程所占用

acquire(int arg) 独占式获取同步状态,如果当前线程同步状态成功,则由该方法返回,否则进入同步队列等待,该方法将会调用可重写的tryAcquire(int arg)方法

acquireInterruptibly(int arg) 与acquire(int arg)方法相同,但是该方法响应中断,当前线程会为获取到同步状态而进入到同步队列中,如果当前线程被中断,则该方法抛出InterruptedException异常并返回

tryAcquireNanos(int arg,long nanos) 超时获取同步状态,如果当前线程在nanos时间内没有获取到同步状态,那么将会返回false,已经获取则返回true

acquireShared(int arg) 共享式获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待,与独占式的主要区别是在同一时刻可以有多个线程获取到同步状态

acquireSharedInterruptibly(int arg) 共享式获取同步状态,响应中断,当前线程会为获取到同步状态而进入到同步队列中,如果当前线程被中断,则该方法抛出InterruptedException异常并返回

tryAcquireSharedNanos(int arg,long nanosTimeout) 共享式获取同步状态,增加超时限制

release(int arg) 独占式释放同步状态,该方法会释放同步状态之后,将同步队列中第一个节点包含的线程唤醒

releaseShared(int arg) 共享式释放同步状态,该方法会释放同步状态之后,将同步队列中第一个节点包含的线程唤醒

6.部分底层锁简介

自旋锁/排队自旋锁/MSC锁/CLH锁

https://coderbee.net/index.php/concurrent/20131115/577

公平锁/非公平锁/可重入锁

独享锁/共享锁

互斥锁/读写锁

乐观锁/悲观锁

分段锁

偏向锁/轻量级锁/重量级锁

https://blog.csdn.net/weixin_38894058/article/details/78952585

7.CLH同步队列

AQS内部维护的FIFO队列,即CLH同步队列

AQS依赖CLH同步队列才能完成同步状态的管理(自旋锁->公平锁,详情了解6章节内容)

https://blog.csdn.net/chenssy/article/details/60781148

CLH同步队列中,一个节点表示一个线程,包含如下:

  1. 线程的引用(thread)
  2. 状态(waitStatus)
  3. 前驱节点(prev)
  4. 后继节点(next)

AQS通过”死循环”(自旋)的方法保证节点的正确添加,只有添加成功后,当前线程才会从该方法返回,否则一直执行

CLH同步队列中,当首节点释放同步状态后,将会唤醒其后继节点,后继节点获取同步状态成功后将自己设为首节点

CLH唤醒方法简述

前驱节点:isLocked=true

后置节点:while(preNode.isLocked){}

后置节点会轮询前驱节点的锁状态,当前驱节点释放锁,isLocked=false,则后置节点跳出”死循环(自旋)”尝试获取锁

7-1.独占式

独占式获取响应中断

AQS提供了acquire(int arg)方法让独占式获取同步状态,但是该方法对中断不响应,线程中断后仍然会在对联中等待获取同步状态,为了响应中断,可以使用acquireInterruptibly(int arg)方法,线程中断后立刻抛出InterruptedException异常

独占式超时获取

tryAcquireNano(int arg,long nanos),如果指定时间没有获得同步状态,则直接返回false,否则返回true

独占式同步状态释放

release(int arg) 释放同步状态

(该方法先调用自定义同步器定义的tryRelease(int arg)方法释放同步状态,释放成功后调用unparkSuccessor(Node node)方法唤醒后继节点)

7-2.共享式

共享式和独占式最大的区别是同一时刻共享式可以有多个线程获得同步状态,而独占式只有一个线程可以获得

共享式读操作可以有多个线程同时执行,写操作同一时刻只有一个线程,其他线程会被阻塞

共享式获取同步状态

AQS提供acquireShared(int arg)方法让共享式获取同步状态,该方法中

首先调用tryAcquireShared(int arg)方法尝试获取同步状态,如果获取失败

则调用doAcquireShared(int arg)自旋方式获取同步状态,共享式获取同步状态的标志是返回>=0

共享式获取同步状态响应中断

AQS提供acquireShared(int arg)方法让共享式获取同步状态,该方法对中断也不响应,需要使用,中断和超时的方法分别为

acquireSharedInterruptibly(int arg)

tryAcquireSharedNanos(int arg,long nanos)

共享式同步状态释放

releaseShared(int arg)释放同步状态

内部调用tryReleaseShared(int arg)来尝试循环释放

因为可能存在多个线程同时释放同步状态资源,所以需要确保同步状态安全成功的释放,一般都是通过CAS和循环来完成

7-3.阻塞和唤醒线程

在线程获取同步状态时,如果获取失败,则加入CLH同步队列,通过自旋的方式不断获取同步状态,但在自选的过程中需要判断该线程是否需要阻塞,

通过acquireQueued()方法

该方法内首先需要判断该线程的状态,再决定是否阻塞

主要通过该节点的前驱节点判断当前线程是否应该被阻塞,规则如下:

  1. 如果当前线程的前驱节点状态为SIGNAL,则表示当前线程需要被阻塞,调用unpark()方法唤醒,直接返回true,当前线程阻塞
  2. 如果当前线程的前驱节点状态为CANCELLED(取消)(ws>0),表示该线程的前驱节点超时或中断
  3. 如果前驱节点非SIGNAL,非CANCELLED,则通过CAS(compareAndSetWaitStatus)的方式将其前驱节点设置为SIGNAL,返回false

如果shouldParkAfterFailedAcquire(Node pred, Node node) 方法返回true,则调用parkAndCheckInterrupt()方法阻塞当前线程;

当前线程release(int arg)释放同步状态后,调用unparkSuccessor(Node node)唤醒后继节点

注意

后继节点可能存在null,如超时/被中断等情况,需要跳过该节点,

所以不采用next的方法,而采用tail回溯的办法找第一个可用线程,

最后调用LockSupport的unpark(Thread thread)方法唤醒该线程

7-4.LockSupport

阻塞或唤醒一个线程时,AQS都会使用LockSupport这个工具类来完成

原语:LockSupport是用来创建锁和其他同步类的基本线程阻塞

每个使用LockSupport的线程都会与一个许可关联,

如果该许可可用,并且可在进程中使用,则调用park()将会立即返回,否则进入阻塞状态

如果许可尚不可用,可以调用unpark使其可用,但是许可不可以重入,只能调用一次park()方法,否则一直进入阻塞状态

LockSupport定义了一系列park开头的方法用来阻塞当前线程,unark(Thread thread)方法唤醒一个被阻塞的线程

park(Object blocker)方法的blocker参数,主要是用来标识当前线程在等待的对象,该对象主要用于问题排查和系统监控。

park方法和unpark(Thread thread)都是成对出现的,同时unpark必须要在park执行之后执行,当然并不是说没有不调用unpark线程就会一直阻塞,park有一个方法,它带了时间戳(parkNanos(long nanos):为了线程调度禁用当前线程,最多等待指定的等待时间,除非许可可用)。

park()和unpark()源码都是调用UNSAFE类中的方法,该方法都是native中的本地方法,比较危险,主要用于执行低级别,不安全的方法集合,

除非是授信代码,否则无法再java程序中直接使用

基础概括传送门

https://my.oschina.net/lifany/blog/146699

教全基础概括传送门

https://blog.csdn.net/windsunmoon/article/details/36903901

参考链接门(含部分源码与流程图):

https://juejin.im/entry/5ae02a7c6fb9a07ac76e7b70

参考资料

Doug Lea:《Java并发编程实战》方腾飞:《Java并发编程的艺术》