# concurrent包了解
第一部分 Atomic数据类型
这部分都被放在java.util.concurrent.atomic这个包里面,实现了原子化操作的数据类型,包括 Boolean, Integer, Long, 和Referrence这四种类型以及这四种类型的数组类型。
第二部分 锁
这部分都被放在java.util.concurrent.lock这个包里面,实现了并发操作中的几种类型的锁
第三部分 java集合框架中的一些数据结构的并发实现
这部分实现的数据结构主要有List, Queue和Map。
第四部分 多线程任务执行
这部分大体上涉及到三个概念,
Callable 被执行的任务
Executor 执行任务
Future 异步提交任务的返回数据
这部分主要是对线程集合的管理的实现,有CyclicBarrier, CountDownLatch,Exchanger等一些类。
### 1、Atomic数据类型
Atomic数据类型有四种类型:AtomicBoolean, AtomicInteger, AtomicLong, 和AtomicReferrence(针对Object的)以及它们的数组类型,还有一个特殊的AtomicStampedReferrence,它不是AtomicReferrence的子类,而是利用AtomicReferrence实现的一个储存引用和Integer组的扩展类。
####CAS####
种无锁机制,比较并交换, 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无 论哪种情况,它都会在 CAS 指令之前返回该位置的值。CAS 有效地说明了"我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可"。
####AtomicXXXX四个数值类型####
1. value成员都是volatile
2. 基本方法get/set
3. compareAndSet
weakCompareAndSet,
lazySet: 使用Unsafe按照顺序更新参考Unsafe的C++实现
getAndSet:取当前值,使用当前值和准备更新的值做CAS
4. 对于Long和Integer
getAndIncrement/incrementAndGet
getAndDecrement/decrementAndGet
getAndAdd/addAndGet
三组方法都和getAndSet,取当前值,加减之得到准备更新的值,再做CAS,/左右的区别在于返回的是当前值还是更新值。
####关于数组####
1. 没有Boolean的Array,可以用Integer代替,底层实现完全一致,毕竟AtomicBoolean底层就是用Integer实现。
2. 数组变量volatile没有意义,因此set/get就需要Unsafe来做了,方法构成与上面一致,但是多了一个index来指定操作数组中的哪一个元素。
```
AtomicIntegerArray 使用
public class AtomicIntegerArrayDemo {
static AtomicIntegerArray arr = new AtomicIntegerArray(10);
public static class AddThread implements Runnable{
@Override
public void run(){
for(int k=0;k<10000;k++){
arr.getAndIncrement(k%arr.length());
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread[]ts=new Thread[10];
for(int k=0;k<10;k++){
ts[k]=new Thread(new AddThread());
}
for(int k=0;k<10;k++){ts[k].start();}
for(int k=0;k<10;k++){ts[k].join();}
System.out.println(arr);
}
}
```
####关于FieldUpdater####
1) 利用反射原理,实现对一个类的某个字段的原子化更新,该字段类型必须和Updater要求的一致,例如如果使用AtomicIntegerFieldUpdater,字段必须是Integer类型,而且必须有volatile限定符。Updater的可以调用的方 法和数字类型完全一致,额外增加一个该类型的对象为参数,updater就会更新该对象的那个字段了。
```
public class AtomicIntegerFieldUpdaterDemo {
public static void main(String[] args) throws InterruptedException {
final Container c= new Container();
for(int i=0;i<1000;i++)
{
Task t=new Task(c);
t.start();
t.join(); //join方法在start后调用,调用线程会等待被调用的线程执行完成后执行
}
System.out.println("============="+c.index);
}
static class Container{
public volatile int index=0;
private String name;
}
}
class Task extends Thread {
//初始化根据那个Field来进行自增
private AtomicIntegerFieldUpdater<AtomicIntegerFieldUpdaterDemo.Container> updater =
AtomicIntegerFieldUpdater.newUpdater(AtomicIntegerFieldUpdaterDemo.Container.class, "index");
private AtomicIntegerFieldUpdaterDemo.Container c;
public Task(AtomicIntegerFieldUpdaterDemo.Container c) {
this.c = c;
}
@Override
public void run() {
System.out.println(updater.getAndIncrement(c));
System.out.println(updater.getAndDecrement(c));
}
}
```
2) Updater本身为抽象类,但有一个私有化的实现,利用门面模式,在抽象类中使用静态方法创建实现。比如我们使用的时候,利用外观模式来处理,统一调用AtomicIntegerFieldUpdater。
```
AtomicIntegerFieldUpdater<Object> updater = AtomicIntegerFieldUpdater.newUpdater(Object, field);
AtomicIntegerFieldUpdaterDemo.Object c;
```
####AtomicMarkableReference和AtomicStampedReference####
AtomicMarkableReference跟AtomicStampedReference差不多, AtomicStampedReference是使用pair的int stamp作为计数器使用,AtomicMarkableReference的pair使用的是boolean mark。 还是那个水的例子,AtomicStampedReference可能关心的是动过几次,AtomicMarkableReference关心的是有没有被人动过,方法都比较简单。
### 2、锁
####Condition####
在使用signal(类似于notify)通知的时候需要实现按照什么样的顺序来通知。
三种等待方式:不中断,一定时间间隔,等到某个时间点。
####Lock和ReadWriteLock####
两个接口都有一个可重入(ReentrantLock, ReentrantReadWriteLock)的实现。
####LockSupport####
工具类,操作对象是线程,基于Unsafe类实现。基本操作park和unpark。park会把使得当前线程失效(没有提供操作其他线程的,其实是可以实现的),暂时挂起,直到出现以下几种情况中的一种:
1)其他线程调用unpark方法操作该线程 2)该线程被中断 3)park方法立刻返回
####AbstractOwnableSynchronizer, AbstractQueuedSynchronizer, AbstractQueuedLongSynchronizer####
**AbstractQueuedSynchronizer的基本介绍**
节点状态:
SIGNAL(-1):当前结点的后继节点将会是阻塞的(通过park方法),因此当前结点需要唤醒他的后继节点,当他释放或取消后。为了避免竞争,获取同步状态的方法必须抢先表示自己需要信号,然后重新原子的获取。最后可能是获取失败,或者再次被阻塞。
CANCELLED(1):由于超时、中断等原因,当前结点会被取消。取消后,节点不会释放状态。特殊情景下,被取消的节点中的线程将不会再被阻塞
CONDITION(-2):当前结点在一个条件队列中,再被转移之前将不会被作为同步节点。被转移时该值会被设置为0。
PROPAGATE(-3):共享式方式释放同步状态后应该被传播到其他节点。这种设置(仅对head节点)在doReleaseShared方法中确保了可以持续,及时有其他的干预操作。
0: 非以上状态
方法解析
doReleaseShared
共享模式下的释放操作,从队首开始向队尾扩散,如果节点的waitStatu是SIGNAL,就唤醒后继节点,如果waitStatus是0,就设置标记成PROPAGATE。
setHeadAndPropagate
把自己设置为head,并且把释放的状态往下传递,这里采用了链式唤醒的方法,1个节点负责唤醒1个后续节点,直到不能唤醒。当后继节点是共享模式isShared,就调用doReleaseShared来唤醒后继节点。
cancelAcquire
取消获取操作,要把节点从同步队列中去除,通过链表操作将它的前置节点的next指向它的后继节点集合。如果该节点是在队尾,直接删除即可,否则要通知后继节点去获取锁
compareAndSetTail
原子的设置尾节点为当前结点,并链接好前后节点。
shouldParkAfterFailedAcquire
将节点的前驱节点的waitStatus设置为SIGNAL,表示会通知后续节点,这样后续节点才能放心去park,而不用担心被丢失唤醒的通知。
selfInterrupt
执行中断
parkAndCheckInterrupt
阻塞并检查是否中断
acquireQueued
监控Node对象在队列中的变化,如果检测到线程中断,返回true,否则返回false.
tryAcquire
获取锁,是否成功。被其集成的方法实现。
doAcquireInterruptibly
监控Node对象在队列中的变化,如果发现中断,直接break,然后取消获取锁的打算。
doAcquireNanos
限时。
setHeadAndPropagate
把自己设置为head,并且把释放的状态往下传递,这里采用了链式唤醒的方法,1个节点负责唤醒1个后续节点,直到不能唤醒。当后继节点是共享模式isShared,就调用doReleaseShared来唤醒后继节点。
doReleaseShared
从head开始往后检查状态,如果节点是SIGNAL状态,就唤醒它的后继节点。如果是0就标记为PROPAGATE, 等它释放锁的时候会再次唤醒后继节点。
tryAcquire
以独占模式获取的尝试查询对象的状态是否允许以独占模式获取它,如果可以的话,就可以获取它。
独占模式释放他
tryRelease
共享模式
tryAcquireShared/tryReleaseShared
疑问:
1、node独占模式和共享模式之间有什么联系?
是否同时有多个线程进入
共享锁就是 读取锁,多个线程读一个资源并不会对资源有所影响;
杜占锁就是 写入锁,多个线程写入资源时,同一时刻只能有一个线程来写。
AbstractOwnableSynchronizer只是实现了被线程独占这些功能的Synchronizer,并不包含如何管理实现多个线程的同步。包含了一个exclusiveOwnerThread,set/get方法。AbstractQueuedSynchronizer利用Queue的方式来管理线程关于锁的使用和同步,相当于一个锁的管理者。
首先关注四个最核心的方法:
protected boolean tryAcquire(int arg)
protected boolean tryRelease(int arg)
protected int tryAcquireShared(int arg)
protected boolean tryReleaseShared(int arg)
前两个用于独占锁,后两者用于共享锁,这四个方法是由子类来实现的,即如何获取和释放锁AbstractQueuedSynchronizer是不参与的,默认实现是不支持,即抛出UnsupportedOperationException。
AbstractQueuedSynchronizer的作用:
当 前线程尝试获取锁的时候,AbstractQueuedSynchronizer会先调用tryAcquire或者tryAcquireShared来尝 试获取,如果得到false,那么把当前线程放到等待队列中去,然后再做进一步操作。我们来分析以下6种情况,前三种用于独占锁,后三者用于共享,独占锁 或者共享锁按照等待方式又分为三种:不可中断线程等待,可中断线程等待,尝试限时等待超时放弃。
这6种的方法都含有一个int类型的参数,这个是给上面的tryAcquire这种方法使用的,也就是说它一个自定义的参数,一般用来表示某个自定义的状态。
1) 独占锁,放入队列后,直到成功获取锁,会忽略线程的中断
2) 独占锁,放入队列后,直到成功获取锁或者遇到中断
3)限时 到一定时间后,直接退出(计算的安全的做法不是一次等待,立刻超时,因为一次等待的时间不一定等于预先设定的值,而是多次等待,累计计算比较安全)。
**AbstractQueuedLongSynchronizer**
AbstractQueuedLongSynchronizer和AbstractQueuedSynchronizer的区别在于acquire和release的arg参数是long而不是int类型。
####ReentrantLock####
所谓可重入锁,就是当一个thread已经获得一个lock的时候,再次请求该锁的时候,会立即返回。使用AbstractQueuedSynchronizer的子类(Sync, NonfairSync, FairSync)进行锁获取释放的管理。
state等于0表示当前没有线程占用锁,下面两个获取锁的过程基本类似,共同的过程是首先检查有没有线程使用该锁,没有的话就占用该并且setState,否则就检查那个占用锁的线程是不是当前线程,如果是的话,仅仅setState,否则就返回false。
Sync#nonfairTryAcquire
```
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
```
FairSync#tryAcquire
```
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (isFirst(current) &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
```
对于FairSync,唯一的不同在于isFirst的调用,而UnfairSync则完全不会检查,谁抢到就是谁的。isFirst会检查有没有线程排队,如果没有,当前线程就可以获得锁,如果有队列,就看当前线程是不是排第一个。
```
final boolean isFirst(Thread current) {
Node h, s;
return ((h = head) == null ||
((s = h.next) != null && s.thread == current) ||
fullIsFirst(current));
}
```
####ReentrantReadWriteLock####
ReentrantReadWriteLock同时管理共享锁(读取锁)和独占锁(写入锁)。
也对应使用AbstractQueuedSynchronizer的子类(Sync, NonfairSync, FairSync)进行锁获取释放的管理。(名字一样但是实现是不同的)。
Sync类
1) Sync的state是32位的,高位的16位是共享锁的状态,低位的16位是独占锁的状态。
```
/*
* Note that tryRelease and tryAcquire can be called by
* Conditions. So it is possible that their arguments contain
* both read and write holds that are all released during a
* condition wait and re-established in tryAcquire.
*/
protected final boolean tryRelease(int releases) {
int nextc = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
// 首先检查独占锁计数,如果是0表示独占锁已经被完全释放,则清除独占锁线程
// 更新状态
if (exclusiveCount(nextc) == 0) {
setExclusiveOwnerThread(null);
setState(nextc);
return true;
} else {
setState(nextc);
return false;
}
}
protected final boolean tryAcquire(int acquires) {
/*
* Walkthrough:
* 1. if read count nonzero or write count nonzero
* and owner is a different thread, fail.
* 2. If count would saturate, fail. (This can only
* happen if count is already nonzero.)
* 3. Otherwise, this thread is eligible for lock if
* it is either a reentrant acquire or
* queue policy allows it. If so, update state
* and set owner.
*/
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);
if (c != 0) {
// c != 0表示有共享锁或者独占锁存在,w == 0表示没有独占锁
// 那么两个条件同时成立表示有共享锁存在,就无法获得独占锁
// 或者有线程拥有独占锁但不是当前线程,那也无权获得独占锁
// (Note: if c != 0 and w == 0 then shared count != 0)
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
}
// 到了这一步,可能有以下几种情况:
// 1) c == 0没有任何锁存在(这个时候 w == 0 也成立)
// 2) 当前线程拥有独占锁,并且还没到锁的最大限制数
// w == 0是当前线程没有独占锁,属于新申请
// writerShouldBlock是抽象方法,对于FairSync和UnfairSync有不同实现
// 该发现检查当前线程申请独占锁应不应该被阻止
// 对于FairSync,writerShouldBlock会用isFirst检查,
// 对于isFirst,如果如果没人排队,或者你是第一个排队的,或者fullIsFirst就返回true
// 对于fullIsFirst,不是很理解
// 对于UnfairSync,writerShouldBlock永远返回false,因为没有排队的概念(体现Unfair)
if ((w == 0 && writerShouldBlock(current)) ||
!compareAndSetState(c, c + acquires))
return false;
// 获取独占锁成功,设置独占锁线程
setExclusiveOwnerThread(current);
return true;
}
// 这里使用HoldCounter类型的ThreadLocal变量存储当前线程拥有的共享锁的计数
// cachedHoldCounter缓存最近一次成功获取共享锁的线程的ThreadLocal变量
protected final boolean tryReleaseShared(int unused) {
HoldCounter rh = cachedHoldCounter;
Thread current = Thread.currentThread();
if (rh == null || rh.tid != current.getId())
rh = readHolds.get();
// tryDecrement()返回拥有的共享锁的计数,大于0则并且更新计数(减1)。
if (rh.tryDecrement() <= 0)
throw new IllegalMonitorStateException();
// 更新共享锁的计数
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT; // 高位的共享锁计数减一
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
protected final int tryAcquireShared(int unused) {
/*
* Walkthrough:
* 1. If write lock held by another thread, fail
* 2. If count saturated, throw error
* 3. Otherwise, this thread is eligible for
* lock wrt state, so ask if it should block
* because of queue policy. If not, try
* to grant by CASing state and updating count.
* Note that step does not check for reentrant
* acquires, which is postponed to full version
* to avoid having to check hold count in
* the more typical non-reentrant case.
* 4. If step 3 fails either because thread
* apparently not eligible or CAS fails,
* chain to version with full retry loop.
*/
Thread current = Thread.currentThread();
int c = getState();
// 其他线程正在使用独占锁
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
// 共享锁计数到达最大限制
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// 类似于writerShouldBlock,readerShouldBlock是抽象方法,有不同实现,
// 检查是不是阻止当前线程共享锁的申请
// 对于UnfairSync,为了防止独占锁饿死的情况,如果发现队列中第一个排队的是独占锁申请,
// 就是block当前共享锁的申请
// 对于FairSync,同样使用isFirst检查当前线程
if (!readerShouldBlock(current) &&
compareAndSetState(c, c + SHARED_UNIT)) {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != current.getId())
cachedHoldCounter = rh = readHolds.get();
rh.count++;
return 1;
}
// 针对CAS失败或者一些不太常见的失败的情况
// 思想:实现常规版本和完整版本(包含所有情况),在常规版本失败的情况下调用完整版本, 提高效率
return fullTryAcquireShared(current);
}
```
fullTryAcquireShared
增加计数缓存,以及红色部分
```
/**
* Full version of acquire for reads, that handles CAS misses
* and reentrant reads not dealt with in tryAcquireShared.
*/
final int fullTryAcquireShared(Thread current) {
/*
* This code is in part redundant with that in
* tryAcquireShared but is simpler overall by not
* complicating tryAcquireShared with interactions between
* retries and lazily reading hold counts.
*/
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != current.getId())
rh = readHolds.get();
for (;;) {
int c = getState();
int w = exclusiveCount(c);
// 红色部分表示没有占用共享锁,新申请共享锁
if ((w != 0 && getExclusiveOwnerThread() != current) ||
((rh.count | w) == 0 && readerShouldBlock(current)))
return -1;
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
if (compareAndSetState(c, c + SHARED_UNIT)) {
cachedHoldCounter = rh; // cache for release
rh.count++;
return 1;
}
}
}
```
####ReadLock和WriteLock####
### 3、同步集合类
####synchronizedMap####
Collections.synchronizedMap(new HashMap());线程安全的map。通过mutex(多线程环境下,无论map的写入或者读取都需要获取mutex锁,会导致所有对map操作的线程进入等待,直到mutex锁可用)实现对map的互斥,但是在多线程环境中的性能表现并不算太好。
####ConcurrentMap####
**锁分段技术**
HashTable容器在竞争激烈的并发环境下表现出效率低下的原因,是因为所有访问HashTable的线程都必须竞争同一把锁,那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁。这里“按顺序”是很重要的,否则极有可能出现死锁,在ConcurrentHashMap内部,段数组是final的,并且其成员变量实际上也是final的,但是,仅仅是将数组声明为final的并不保证数组成员也是final的,这需要实现上的保证。这可以确保不会出现死锁,因为获得锁的顺序是固定的。
! [](https://i.imgur.com/pbfGeX8.png)
ConcurrentHashMap的基本思想是采取分块的方式加锁,分块数由参数“concurrencyLevel”来决定(和HashMap中的“initialCapacity”类似,实际块数是第一个大于concurrencyLevel的2的n次方)。每个分块被称为Segment,Segment的索引方式和HashMap中的Entry索引方式一致(hash值对数组长度取模)。
对Segment加锁的方式很简单,直接把Segment定义为ReentrantLock的子类。同时Segment又是一个特定实现的hash table。
hash获取的问题:
```
private int hash(Object k) {
int h = hashSeed;
if ((0 != h) && (k instanceof String)) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
// Spread bits to regularize both segment and index locations,
// using variant of single-word Wang/Jenkins hash.
h += (h << 15) ^ 0xffffcd7d;
h ^= (h >>> 10);
h += (h << 3);
h ^= (h >>> 6);
h += (h << 2) + (h << 14);
return h ^ (h >>> 16);
}
private transient final int hashSeed = randomHashSeed(this);
private static int randomHashSeed(ConcurrentHashMap instance) {
if (sun.misc.VM.isBooted() && Holder.ALTERNATIVE_HASHING) {
return sun.misc.Hashing.randomHashSeed(instance);
}
return 0;
}
```
如果key是String,则使用stringHash32直接对key做hash;如果key不是String,则对key的hashCode,进行复杂的位运算,使得二进制数中1的分布更加均匀。这样做的好处,就是尽量做到hash值不重复。因为hash不重复的数据,在寻址时,能够通过key的hash值,一次性找到,而如果hash多次重复,则数据会以链表的形式存储,根据key获取值时,只能遍历查找效率极低的链表(不懂的话,先略过,一会儿再回头来看)。
注意:sun.misc.Hashing.randomHashSeed后续会调用java.util.Random.nextInt方法。Random类以其多线程环境下不友好而闻名:它有一个Atomic类型的成员变量private final AtomicLong seedfield。Atomic类型在多线程竞争程度较低或者适中的场景下性能表现较好,但在竞争激烈的场景下性能很差。上面的代码可以看到,ConcurrentHash仅在系统设置了jdk.map.althashing.threshold才启用。
```
static final class Segment<K,V> extends ReentrantLock implements Serializable
```
GET代码
```
public V get(Object key) {
//定义的segment就是把原来Array<Entry>拆成小块了
Segment<K,V> s; // manually integrate access methods to reduce overhead
HashEntry<K,V>[] tab;
//获取当前key的hash值
int h = hash(key);
// 根据Segment的索引((h >>> segmentShift) & segmentMask)算出在Segment[]上的偏移量
long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
// 若Segment存在,则继续查找table[]的索引位置;
// 根据table的索引((tab.length - 1) & h)算出在table[]上的偏移量,循环链表找出结果
if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
(tab = s.table) != null) {
for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
(tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
e != null; e = e.next) {
K k;
if ((k = e.key) == key || (e.hash == h && key.equals(k)))
return e.value;
}
}
return null;
}
```
PUT代码
可以看到,读取的时候没有调用的Segment的获取锁的方法,而是通过hash值定位到Entry,然后遍历Entry的链表。为什么这里不用加锁呢?看看HashEntry的代码就会明白了。
```
static final class HashEntry<K,V> {
final int hash;
final K key;
volatile V value;
volatile HashEntry<K,V> next;
}
```
value和next属性是带有volatile修饰符的,可以大胆放心的遍历和比较。
```
public V put(K key, V value) {
Segment<K,V> s;
if (value == null)
throw new NullPointerException();
int hash = hash(key);
int j = (hash >>> segmentShift) & segmentMask;
if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck
(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
s = ensureSegment(j);
return s.put(key, hash, value, false);
}
```
####CountDownLatch####
倒计时、门栓。主线程在CountDownLatch等待,当所有的检查任务全部完成后,主线程才能继续执行。
####CyclicBarrier####
和CountDownLatch的基本相似,唯一的强项是可以传入一个action,当达标时。
####LockSupport####
park和unpark方法来替代suspend和resume(suspend可能导致线程永久卡死;resume可能导致线程无法继续运行),与Object.wait相比,它不需要先获得某个对象的锁,也不会抛出InterruptedException异常。park和unpark的执行顺序并不会影响线程的继续执行。
####BlockingQueue####
阻塞队列实现数据共享通道。BlockingQueue是一个接口,并有ArrayBlockingQueue和LinkedBlockingQueue实现。
####ConcurrentLinkedQueue####
使用CAS(内存位置(V)、预期原值(A)和新值(B))保证线程的安全。CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。
####CopyOnWriteArrayList和CopyOnWriteArraySet####
适用于读多写少的操作; CopyOnWrite在写入操作的时候,进行一次自我复制,换句话说,当整个List需要修改时,并不修改原有的内容,而是对原有的数据进行一次复制,并将修改的内容写入到副本里。写完之后,再将修改完的副本替换为原来的数据。修改的时候利用锁,修改完毕后在newElements中加入新元素,最后使用新数组替换老数组即可。因为array变量时volatile类型,修改完之后,读操作能立刻获得。
####SkipList####
跳表类似Redis中跳表。跳表是有序的,
### 4、线程池
不需要重复的制造轮子,复用即可。
####设定线程池的大小####
ExecutorService作为接口;
####计划任务的实现####
ScheduledExecutorService类;
scheduleAtFixedRate 任务按照 initialDelay + n*period的时间点来执行。
scheduleWithFixedDelay 下一个任务在上一个任务完成后的时间差才开始执行。
####线程池的原理####
分析ThreadPoolExecutor类:
corePoolSize:指定线程池中线程数量;
maximumPoolSize:线程池中最大的线程数量;
keepAliveTime:超过corePoolSize的线程可以生存多久时间;
unit:时间单位;
workQueue:任务队列,被提交但尚未被执行的任务;
threadFactory:线程工厂,用于创建线程;
handler:拒绝策略,任务过多来不及处理时,如何拒绝任务;
解析workQueue:
直接提交队列>
有界的任务队列>
无界的任务队列>
优先任务队列>
拒绝策略:
直接抛出异常>
运行当前被丢弃的线程>
丢弃最老的一个请求>
丢弃无法处理的请求,不予任何处理>
ThreadFactory 线程池;
ThreadPoolExecutor扩展线程池,可提供beforeExecute()|afterExecuto()|terminated()三个接口可以对线程进行控制;
**合理设置线程池的大小**
Ncpu *Ucpu*(1+W/C)
Ncpu表示cpu的总数量;Ucpu表示可用cpu的数量;W/C表示等待时间与计算时间的比值;
综上,可以发现计算时间越短,线程池越大。如果计算时间过大,那么线程池应该小点;
**线程池中需要堆栈**
线程池默认情况下不会打印出堆栈信息,在查找时非常痛苦的。
1、放弃使用submit(),改用execute();
2、改造submit(),比如:
```
Future re=pools.submit(new DivTask(100,i));
re.get();
```
3、封装submit、execute,自己创建一个clientTrace()方法,并在异常的时候打印。
```
public Exception clientTrace(){
return new Exception("Client stack trace");
}
```
**Fork/Join框架**
分而治之的逻辑,使用fork提交任务,join等待直到所有的任务都完成。
ForkJoinTask支持fork()分解和join()等待任务。RecursiveAction(没有返回值)和RecusiveTask(可携带返回值)继承。
```
package com.paic.fcs.remit.utils.demo;
import java.util.ArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
/**
* Created by LAIXIAO820 on 2018/1/24.
* version
*/
public class CountTask extends RecursiveTask {
private static final int THRESHOLD=1000;
private long start;
private long end;
public CountTask(long start, long end){
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
long sum=0;
boolean canCompute = (end-start) <THRESHOLD;
if (canCompute) {
for (long i=start;i<=end;i++) {
sum+=i;
}
}else {
long step = (start+end)/100;
ArrayList<CountTask> subTasks = new ArrayList<CountTask>();
long pos = start;
for (int i=0;i<100;i++){
//一个线程处理的数量
long lastOne= pos + step;
if (lastOne>end) lastOne =end;
CountTask subTask = new CountTask(pos, lastOne);
pos+=step+1;
subTasks.add(subTask);
subTask.fork();
}
for (CountTask task : subTasks) {
sum += (long)task.join();
}
}
return sum;
}
public static void main(String[] args){
ForkJoinPool pool = new ForkJoinPool();
CountTask task = new CountTask(0, 1000L);
ForkJoinTask<Long> result= pool.submit(task);
long res = 0;
try {
res = result.get();
System.out.println("sum="+res);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
```
第一部分 Atomic数据类型
这部分都被放在java.util.concurrent.atomic这个包里面,实现了原子化操作的数据类型,包括 Boolean, Integer, Long, 和Referrence这四种类型以及这四种类型的数组类型。
第二部分 锁
这部分都被放在java.util.concurrent.lock这个包里面,实现了并发操作中的几种类型的锁
第三部分 java集合框架中的一些数据结构的并发实现
这部分实现的数据结构主要有List, Queue和Map。
第四部分 多线程任务执行
这部分大体上涉及到三个概念,
Callable 被执行的任务
Executor 执行任务
Future 异步提交任务的返回数据
这部分主要是对线程集合的管理的实现,有CyclicBarrier, CountDownLatch,Exchanger等一些类。
### 1、Atomic数据类型
Atomic数据类型有四种类型:AtomicBoolean, AtomicInteger, AtomicLong, 和AtomicReferrence(针对Object的)以及它们的数组类型,还有一个特殊的AtomicStampedReferrence,它不是AtomicReferrence的子类,而是利用AtomicReferrence实现的一个储存引用和Integer组的扩展类。
####CAS####
种无锁机制,比较并交换, 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无 论哪种情况,它都会在 CAS 指令之前返回该位置的值。CAS 有效地说明了"我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可"。
####AtomicXXXX四个数值类型####
1. value成员都是volatile
2. 基本方法get/set
3. compareAndSet
weakCompareAndSet,
lazySet: 使用Unsafe按照顺序更新参考Unsafe的C++实现
getAndSet:取当前值,使用当前值和准备更新的值做CAS
4. 对于Long和Integer
getAndIncrement/incrementAndGet
getAndDecrement/decrementAndGet
getAndAdd/addAndGet
三组方法都和getAndSet,取当前值,加减之得到准备更新的值,再做CAS,/左右的区别在于返回的是当前值还是更新值。
####关于数组####
1. 没有Boolean的Array,可以用Integer代替,底层实现完全一致,毕竟AtomicBoolean底层就是用Integer实现。
2. 数组变量volatile没有意义,因此set/get就需要Unsafe来做了,方法构成与上面一致,但是多了一个index来指定操作数组中的哪一个元素。
```
AtomicIntegerArray 使用
public class AtomicIntegerArrayDemo {
static AtomicIntegerArray arr = new AtomicIntegerArray(10);
public static class AddThread implements Runnable{
@Override
public void run(){
for(int k=0;k<10000;k++){
arr.getAndIncrement(k%arr.length());
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread[]ts=new Thread[10];
for(int k=0;k<10;k++){
ts[k]=new Thread(new AddThread());
}
for(int k=0;k<10;k++){ts[k].start();}
for(int k=0;k<10;k++){ts[k].join();}
System.out.println(arr);
}
}
```
####关于FieldUpdater####
1) 利用反射原理,实现对一个类的某个字段的原子化更新,该字段类型必须和Updater要求的一致,例如如果使用AtomicIntegerFieldUpdater,字段必须是Integer类型,而且必须有volatile限定符。Updater的可以调用的方 法和数字类型完全一致,额外增加一个该类型的对象为参数,updater就会更新该对象的那个字段了。
```
public class AtomicIntegerFieldUpdaterDemo {
public static void main(String[] args) throws InterruptedException {
final Container c= new Container();
for(int i=0;i<1000;i++)
{
Task t=new Task(c);
t.start();
t.join(); //join方法在start后调用,调用线程会等待被调用的线程执行完成后执行
}
System.out.println("============="+c.index);
}
static class Container{
public volatile int index=0;
private String name;
}
}
class Task extends Thread {
//初始化根据那个Field来进行自增
private AtomicIntegerFieldUpdater<AtomicIntegerFieldUpdaterDemo.Container> updater =
AtomicIntegerFieldUpdater.newUpdater(AtomicIntegerFieldUpdaterDemo.Container.class, "index");
private AtomicIntegerFieldUpdaterDemo.Container c;
public Task(AtomicIntegerFieldUpdaterDemo.Container c) {
this.c = c;
}
@Override
public void run() {
System.out.println(updater.getAndIncrement(c));
System.out.println(updater.getAndDecrement(c));
}
}
```
2) Updater本身为抽象类,但有一个私有化的实现,利用门面模式,在抽象类中使用静态方法创建实现。比如我们使用的时候,利用外观模式来处理,统一调用AtomicIntegerFieldUpdater。
```
AtomicIntegerFieldUpdater<Object> updater = AtomicIntegerFieldUpdater.newUpdater(Object, field);
AtomicIntegerFieldUpdaterDemo.Object c;
```
####AtomicMarkableReference和AtomicStampedReference####
AtomicMarkableReference跟AtomicStampedReference差不多, AtomicStampedReference是使用pair的int stamp作为计数器使用,AtomicMarkableReference的pair使用的是boolean mark。 还是那个水的例子,AtomicStampedReference可能关心的是动过几次,AtomicMarkableReference关心的是有没有被人动过,方法都比较简单。
### 2、锁
####Condition####
在使用signal(类似于notify)通知的时候需要实现按照什么样的顺序来通知。
三种等待方式:不中断,一定时间间隔,等到某个时间点。
####Lock和ReadWriteLock####
两个接口都有一个可重入(ReentrantLock, ReentrantReadWriteLock)的实现。
####LockSupport####
工具类,操作对象是线程,基于Unsafe类实现。基本操作park和unpark。park会把使得当前线程失效(没有提供操作其他线程的,其实是可以实现的),暂时挂起,直到出现以下几种情况中的一种:
1)其他线程调用unpark方法操作该线程 2)该线程被中断 3)park方法立刻返回
####AbstractOwnableSynchronizer, AbstractQueuedSynchronizer, AbstractQueuedLongSynchronizer####
**AbstractQueuedSynchronizer的基本介绍**
节点状态:
SIGNAL(-1):当前结点的后继节点将会是阻塞的(通过park方法),因此当前结点需要唤醒他的后继节点,当他释放或取消后。为了避免竞争,获取同步状态的方法必须抢先表示自己需要信号,然后重新原子的获取。最后可能是获取失败,或者再次被阻塞。
CANCELLED(1):由于超时、中断等原因,当前结点会被取消。取消后,节点不会释放状态。特殊情景下,被取消的节点中的线程将不会再被阻塞
CONDITION(-2):当前结点在一个条件队列中,再被转移之前将不会被作为同步节点。被转移时该值会被设置为0。
PROPAGATE(-3):共享式方式释放同步状态后应该被传播到其他节点。这种设置(仅对head节点)在doReleaseShared方法中确保了可以持续,及时有其他的干预操作。
0: 非以上状态
方法解析
doReleaseShared
共享模式下的释放操作,从队首开始向队尾扩散,如果节点的waitStatu是SIGNAL,就唤醒后继节点,如果waitStatus是0,就设置标记成PROPAGATE。
setHeadAndPropagate
把自己设置为head,并且把释放的状态往下传递,这里采用了链式唤醒的方法,1个节点负责唤醒1个后续节点,直到不能唤醒。当后继节点是共享模式isShared,就调用doReleaseShared来唤醒后继节点。
cancelAcquire
取消获取操作,要把节点从同步队列中去除,通过链表操作将它的前置节点的next指向它的后继节点集合。如果该节点是在队尾,直接删除即可,否则要通知后继节点去获取锁
compareAndSetTail
原子的设置尾节点为当前结点,并链接好前后节点。
shouldParkAfterFailedAcquire
将节点的前驱节点的waitStatus设置为SIGNAL,表示会通知后续节点,这样后续节点才能放心去park,而不用担心被丢失唤醒的通知。
selfInterrupt
执行中断
parkAndCheckInterrupt
阻塞并检查是否中断
acquireQueued
监控Node对象在队列中的变化,如果检测到线程中断,返回true,否则返回false.
tryAcquire
获取锁,是否成功。被其集成的方法实现。
doAcquireInterruptibly
监控Node对象在队列中的变化,如果发现中断,直接break,然后取消获取锁的打算。
doAcquireNanos
限时。
setHeadAndPropagate
把自己设置为head,并且把释放的状态往下传递,这里采用了链式唤醒的方法,1个节点负责唤醒1个后续节点,直到不能唤醒。当后继节点是共享模式isShared,就调用doReleaseShared来唤醒后继节点。
doReleaseShared
从head开始往后检查状态,如果节点是SIGNAL状态,就唤醒它的后继节点。如果是0就标记为PROPAGATE, 等它释放锁的时候会再次唤醒后继节点。
tryAcquire
以独占模式获取的尝试查询对象的状态是否允许以独占模式获取它,如果可以的话,就可以获取它。
独占模式释放他
tryRelease
共享模式
tryAcquireShared/tryReleaseShared
疑问:
1、node独占模式和共享模式之间有什么联系?
是否同时有多个线程进入
共享锁就是 读取锁,多个线程读一个资源并不会对资源有所影响;
杜占锁就是 写入锁,多个线程写入资源时,同一时刻只能有一个线程来写。
AbstractOwnableSynchronizer只是实现了被线程独占这些功能的Synchronizer,并不包含如何管理实现多个线程的同步。包含了一个exclusiveOwnerThread,set/get方法。AbstractQueuedSynchronizer利用Queue的方式来管理线程关于锁的使用和同步,相当于一个锁的管理者。
首先关注四个最核心的方法:
protected boolean tryAcquire(int arg)
protected boolean tryRelease(int arg)
protected int tryAcquireShared(int arg)
protected boolean tryReleaseShared(int arg)
前两个用于独占锁,后两者用于共享锁,这四个方法是由子类来实现的,即如何获取和释放锁AbstractQueuedSynchronizer是不参与的,默认实现是不支持,即抛出UnsupportedOperationException。
AbstractQueuedSynchronizer的作用:
当 前线程尝试获取锁的时候,AbstractQueuedSynchronizer会先调用tryAcquire或者tryAcquireShared来尝 试获取,如果得到false,那么把当前线程放到等待队列中去,然后再做进一步操作。我们来分析以下6种情况,前三种用于独占锁,后三者用于共享,独占锁 或者共享锁按照等待方式又分为三种:不可中断线程等待,可中断线程等待,尝试限时等待超时放弃。
这6种的方法都含有一个int类型的参数,这个是给上面的tryAcquire这种方法使用的,也就是说它一个自定义的参数,一般用来表示某个自定义的状态。
1) 独占锁,放入队列后,直到成功获取锁,会忽略线程的中断
2) 独占锁,放入队列后,直到成功获取锁或者遇到中断
3)限时 到一定时间后,直接退出(计算的安全的做法不是一次等待,立刻超时,因为一次等待的时间不一定等于预先设定的值,而是多次等待,累计计算比较安全)。
**AbstractQueuedLongSynchronizer**
AbstractQueuedLongSynchronizer和AbstractQueuedSynchronizer的区别在于acquire和release的arg参数是long而不是int类型。
####ReentrantLock####
所谓可重入锁,就是当一个thread已经获得一个lock的时候,再次请求该锁的时候,会立即返回。使用AbstractQueuedSynchronizer的子类(Sync, NonfairSync, FairSync)进行锁获取释放的管理。
state等于0表示当前没有线程占用锁,下面两个获取锁的过程基本类似,共同的过程是首先检查有没有线程使用该锁,没有的话就占用该并且setState,否则就检查那个占用锁的线程是不是当前线程,如果是的话,仅仅setState,否则就返回false。
Sync#nonfairTryAcquire
```
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
```
FairSync#tryAcquire
```
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (isFirst(current) &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
```
对于FairSync,唯一的不同在于isFirst的调用,而UnfairSync则完全不会检查,谁抢到就是谁的。isFirst会检查有没有线程排队,如果没有,当前线程就可以获得锁,如果有队列,就看当前线程是不是排第一个。
```
final boolean isFirst(Thread current) {
Node h, s;
return ((h = head) == null ||
((s = h.next) != null && s.thread == current) ||
fullIsFirst(current));
}
```
####ReentrantReadWriteLock####
ReentrantReadWriteLock同时管理共享锁(读取锁)和独占锁(写入锁)。
也对应使用AbstractQueuedSynchronizer的子类(Sync, NonfairSync, FairSync)进行锁获取释放的管理。(名字一样但是实现是不同的)。
Sync类
1) Sync的state是32位的,高位的16位是共享锁的状态,低位的16位是独占锁的状态。
```
/*
* Note that tryRelease and tryAcquire can be called by
* Conditions. So it is possible that their arguments contain
* both read and write holds that are all released during a
* condition wait and re-established in tryAcquire.
*/
protected final boolean tryRelease(int releases) {
int nextc = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
// 首先检查独占锁计数,如果是0表示独占锁已经被完全释放,则清除独占锁线程
// 更新状态
if (exclusiveCount(nextc) == 0) {
setExclusiveOwnerThread(null);
setState(nextc);
return true;
} else {
setState(nextc);
return false;
}
}
protected final boolean tryAcquire(int acquires) {
/*
* Walkthrough:
* 1. if read count nonzero or write count nonzero
* and owner is a different thread, fail.
* 2. If count would saturate, fail. (This can only
* happen if count is already nonzero.)
* 3. Otherwise, this thread is eligible for lock if
* it is either a reentrant acquire or
* queue policy allows it. If so, update state
* and set owner.
*/
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);
if (c != 0) {
// c != 0表示有共享锁或者独占锁存在,w == 0表示没有独占锁
// 那么两个条件同时成立表示有共享锁存在,就无法获得独占锁
// 或者有线程拥有独占锁但不是当前线程,那也无权获得独占锁
// (Note: if c != 0 and w == 0 then shared count != 0)
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
}
// 到了这一步,可能有以下几种情况:
// 1) c == 0没有任何锁存在(这个时候 w == 0 也成立)
// 2) 当前线程拥有独占锁,并且还没到锁的最大限制数
// w == 0是当前线程没有独占锁,属于新申请
// writerShouldBlock是抽象方法,对于FairSync和UnfairSync有不同实现
// 该发现检查当前线程申请独占锁应不应该被阻止
// 对于FairSync,writerShouldBlock会用isFirst检查,
// 对于isFirst,如果如果没人排队,或者你是第一个排队的,或者fullIsFirst就返回true
// 对于fullIsFirst,不是很理解
// 对于UnfairSync,writerShouldBlock永远返回false,因为没有排队的概念(体现Unfair)
if ((w == 0 && writerShouldBlock(current)) ||
!compareAndSetState(c, c + acquires))
return false;
// 获取独占锁成功,设置独占锁线程
setExclusiveOwnerThread(current);
return true;
}
// 这里使用HoldCounter类型的ThreadLocal变量存储当前线程拥有的共享锁的计数
// cachedHoldCounter缓存最近一次成功获取共享锁的线程的ThreadLocal变量
protected final boolean tryReleaseShared(int unused) {
HoldCounter rh = cachedHoldCounter;
Thread current = Thread.currentThread();
if (rh == null || rh.tid != current.getId())
rh = readHolds.get();
// tryDecrement()返回拥有的共享锁的计数,大于0则并且更新计数(减1)。
if (rh.tryDecrement() <= 0)
throw new IllegalMonitorStateException();
// 更新共享锁的计数
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT; // 高位的共享锁计数减一
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
protected final int tryAcquireShared(int unused) {
/*
* Walkthrough:
* 1. If write lock held by another thread, fail
* 2. If count saturated, throw error
* 3. Otherwise, this thread is eligible for
* lock wrt state, so ask if it should block
* because of queue policy. If not, try
* to grant by CASing state and updating count.
* Note that step does not check for reentrant
* acquires, which is postponed to full version
* to avoid having to check hold count in
* the more typical non-reentrant case.
* 4. If step 3 fails either because thread
* apparently not eligible or CAS fails,
* chain to version with full retry loop.
*/
Thread current = Thread.currentThread();
int c = getState();
// 其他线程正在使用独占锁
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
// 共享锁计数到达最大限制
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// 类似于writerShouldBlock,readerShouldBlock是抽象方法,有不同实现,
// 检查是不是阻止当前线程共享锁的申请
// 对于UnfairSync,为了防止独占锁饿死的情况,如果发现队列中第一个排队的是独占锁申请,
// 就是block当前共享锁的申请
// 对于FairSync,同样使用isFirst检查当前线程
if (!readerShouldBlock(current) &&
compareAndSetState(c, c + SHARED_UNIT)) {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != current.getId())
cachedHoldCounter = rh = readHolds.get();
rh.count++;
return 1;
}
// 针对CAS失败或者一些不太常见的失败的情况
// 思想:实现常规版本和完整版本(包含所有情况),在常规版本失败的情况下调用完整版本, 提高效率
return fullTryAcquireShared(current);
}
```
fullTryAcquireShared
增加计数缓存,以及红色部分
```
/**
* Full version of acquire for reads, that handles CAS misses
* and reentrant reads not dealt with in tryAcquireShared.
*/
final int fullTryAcquireShared(Thread current) {
/*
* This code is in part redundant with that in
* tryAcquireShared but is simpler overall by not
* complicating tryAcquireShared with interactions between
* retries and lazily reading hold counts.
*/
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != current.getId())
rh = readHolds.get();
for (;;) {
int c = getState();
int w = exclusiveCount(c);
// 红色部分表示没有占用共享锁,新申请共享锁
if ((w != 0 && getExclusiveOwnerThread() != current) ||
((rh.count | w) == 0 && readerShouldBlock(current)))
return -1;
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
if (compareAndSetState(c, c + SHARED_UNIT)) {
cachedHoldCounter = rh; // cache for release
rh.count++;
return 1;
}
}
}
```
####ReadLock和WriteLock####
### 3、同步集合类
####synchronizedMap####
Collections.synchronizedMap(new HashMap());线程安全的map。通过mutex(多线程环境下,无论map的写入或者读取都需要获取mutex锁,会导致所有对map操作的线程进入等待,直到mutex锁可用)实现对map的互斥,但是在多线程环境中的性能表现并不算太好。
####ConcurrentMap####
**锁分段技术**
HashTable容器在竞争激烈的并发环境下表现出效率低下的原因,是因为所有访问HashTable的线程都必须竞争同一把锁,那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。有些方法需要跨段,比如size()和containsValue(),它们可能需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁。这里“按顺序”是很重要的,否则极有可能出现死锁,在ConcurrentHashMap内部,段数组是final的,并且其成员变量实际上也是final的,但是,仅仅是将数组声明为final的并不保证数组成员也是final的,这需要实现上的保证。这可以确保不会出现死锁,因为获得锁的顺序是固定的。
! [](https://i.imgur.com/pbfGeX8.png)
ConcurrentHashMap的基本思想是采取分块的方式加锁,分块数由参数“concurrencyLevel”来决定(和HashMap中的“initialCapacity”类似,实际块数是第一个大于concurrencyLevel的2的n次方)。每个分块被称为Segment,Segment的索引方式和HashMap中的Entry索引方式一致(hash值对数组长度取模)。
对Segment加锁的方式很简单,直接把Segment定义为ReentrantLock的子类。同时Segment又是一个特定实现的hash table。
hash获取的问题:
```
private int hash(Object k) {
int h = hashSeed;
if ((0 != h) && (k instanceof String)) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
// Spread bits to regularize both segment and index locations,
// using variant of single-word Wang/Jenkins hash.
h += (h << 15) ^ 0xffffcd7d;
h ^= (h >>> 10);
h += (h << 3);
h ^= (h >>> 6);
h += (h << 2) + (h << 14);
return h ^ (h >>> 16);
}
private transient final int hashSeed = randomHashSeed(this);
private static int randomHashSeed(ConcurrentHashMap instance) {
if (sun.misc.VM.isBooted() && Holder.ALTERNATIVE_HASHING) {
return sun.misc.Hashing.randomHashSeed(instance);
}
return 0;
}
```
如果key是String,则使用stringHash32直接对key做hash;如果key不是String,则对key的hashCode,进行复杂的位运算,使得二进制数中1的分布更加均匀。这样做的好处,就是尽量做到hash值不重复。因为hash不重复的数据,在寻址时,能够通过key的hash值,一次性找到,而如果hash多次重复,则数据会以链表的形式存储,根据key获取值时,只能遍历查找效率极低的链表(不懂的话,先略过,一会儿再回头来看)。
注意:sun.misc.Hashing.randomHashSeed后续会调用java.util.Random.nextInt方法。Random类以其多线程环境下不友好而闻名:它有一个Atomic类型的成员变量private final AtomicLong seedfield。Atomic类型在多线程竞争程度较低或者适中的场景下性能表现较好,但在竞争激烈的场景下性能很差。上面的代码可以看到,ConcurrentHash仅在系统设置了jdk.map.althashing.threshold才启用。
```
static final class Segment<K,V> extends ReentrantLock implements Serializable
```
GET代码
```
public V get(Object key) {
//定义的segment就是把原来Array<Entry>拆成小块了
Segment<K,V> s; // manually integrate access methods to reduce overhead
HashEntry<K,V>[] tab;
//获取当前key的hash值
int h = hash(key);
// 根据Segment的索引((h >>> segmentShift) & segmentMask)算出在Segment[]上的偏移量
long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
// 若Segment存在,则继续查找table[]的索引位置;
// 根据table的索引((tab.length - 1) & h)算出在table[]上的偏移量,循环链表找出结果
if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
(tab = s.table) != null) {
for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
(tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
e != null; e = e.next) {
K k;
if ((k = e.key) == key || (e.hash == h && key.equals(k)))
return e.value;
}
}
return null;
}
```
PUT代码
可以看到,读取的时候没有调用的Segment的获取锁的方法,而是通过hash值定位到Entry,然后遍历Entry的链表。为什么这里不用加锁呢?看看HashEntry的代码就会明白了。
```
static final class HashEntry<K,V> {
final int hash;
final K key;
volatile V value;
volatile HashEntry<K,V> next;
}
```
value和next属性是带有volatile修饰符的,可以大胆放心的遍历和比较。
```
public V put(K key, V value) {
Segment<K,V> s;
if (value == null)
throw new NullPointerException();
int hash = hash(key);
int j = (hash >>> segmentShift) & segmentMask;
if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck
(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
s = ensureSegment(j);
return s.put(key, hash, value, false);
}
```
####CountDownLatch####
倒计时、门栓。主线程在CountDownLatch等待,当所有的检查任务全部完成后,主线程才能继续执行。
####CyclicBarrier####
和CountDownLatch的基本相似,唯一的强项是可以传入一个action,当达标时。
####LockSupport####
park和unpark方法来替代suspend和resume(suspend可能导致线程永久卡死;resume可能导致线程无法继续运行),与Object.wait相比,它不需要先获得某个对象的锁,也不会抛出InterruptedException异常。park和unpark的执行顺序并不会影响线程的继续执行。
####BlockingQueue####
阻塞队列实现数据共享通道。BlockingQueue是一个接口,并有ArrayBlockingQueue和LinkedBlockingQueue实现。
####ConcurrentLinkedQueue####
使用CAS(内存位置(V)、预期原值(A)和新值(B))保证线程的安全。CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。
####CopyOnWriteArrayList和CopyOnWriteArraySet####
适用于读多写少的操作; CopyOnWrite在写入操作的时候,进行一次自我复制,换句话说,当整个List需要修改时,并不修改原有的内容,而是对原有的数据进行一次复制,并将修改的内容写入到副本里。写完之后,再将修改完的副本替换为原来的数据。修改的时候利用锁,修改完毕后在newElements中加入新元素,最后使用新数组替换老数组即可。因为array变量时volatile类型,修改完之后,读操作能立刻获得。
####SkipList####
跳表类似Redis中跳表。跳表是有序的,
### 4、线程池
不需要重复的制造轮子,复用即可。
####设定线程池的大小####
ExecutorService作为接口;
####计划任务的实现####
ScheduledExecutorService类;
scheduleAtFixedRate 任务按照 initialDelay + n*period的时间点来执行。
scheduleWithFixedDelay 下一个任务在上一个任务完成后的时间差才开始执行。
####线程池的原理####
分析ThreadPoolExecutor类:
corePoolSize:指定线程池中线程数量;
maximumPoolSize:线程池中最大的线程数量;
keepAliveTime:超过corePoolSize的线程可以生存多久时间;
unit:时间单位;
workQueue:任务队列,被提交但尚未被执行的任务;
threadFactory:线程工厂,用于创建线程;
handler:拒绝策略,任务过多来不及处理时,如何拒绝任务;
解析workQueue:
直接提交队列>
有界的任务队列>
无界的任务队列>
优先任务队列>
拒绝策略:
直接抛出异常>
运行当前被丢弃的线程>
丢弃最老的一个请求>
丢弃无法处理的请求,不予任何处理>
ThreadFactory 线程池;
ThreadPoolExecutor扩展线程池,可提供beforeExecute()|afterExecuto()|terminated()三个接口可以对线程进行控制;
**合理设置线程池的大小**
Ncpu *Ucpu*(1+W/C)
Ncpu表示cpu的总数量;Ucpu表示可用cpu的数量;W/C表示等待时间与计算时间的比值;
综上,可以发现计算时间越短,线程池越大。如果计算时间过大,那么线程池应该小点;
**线程池中需要堆栈**
线程池默认情况下不会打印出堆栈信息,在查找时非常痛苦的。
1、放弃使用submit(),改用execute();
2、改造submit(),比如:
```
Future re=pools.submit(new DivTask(100,i));
re.get();
```
3、封装submit、execute,自己创建一个clientTrace()方法,并在异常的时候打印。
```
public Exception clientTrace(){
return new Exception("Client stack trace");
}
```
**Fork/Join框架**
分而治之的逻辑,使用fork提交任务,join等待直到所有的任务都完成。
ForkJoinTask支持fork()分解和join()等待任务。RecursiveAction(没有返回值)和RecusiveTask(可携带返回值)继承。
```
package com.paic.fcs.remit.utils.demo;
import java.util.ArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
/**
* Created by LAIXIAO820 on 2018/1/24.
* version
*/
public class CountTask extends RecursiveTask {
private static final int THRESHOLD=1000;
private long start;
private long end;
public CountTask(long start, long end){
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
long sum=0;
boolean canCompute = (end-start) <THRESHOLD;
if (canCompute) {
for (long i=start;i<=end;i++) {
sum+=i;
}
}else {
long step = (start+end)/100;
ArrayList<CountTask> subTasks = new ArrayList<CountTask>();
long pos = start;
for (int i=0;i<100;i++){
//一个线程处理的数量
long lastOne= pos + step;
if (lastOne>end) lastOne =end;
CountTask subTask = new CountTask(pos, lastOne);
pos+=step+1;
subTasks.add(subTask);
subTask.fork();
}
for (CountTask task : subTasks) {
sum += (long)task.join();
}
}
return sum;
}
public static void main(String[] args){
ForkJoinPool pool = new ForkJoinPool();
CountTask task = new CountTask(0, 1000L);
ForkJoinTask<Long> result= pool.submit(task);
long res = 0;
try {
res = result.get();
System.out.println("sum="+res);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
```