1、分类
Atomic包,locks包,线程池&并发集合。
2、并发集合
(1)ConcurrentHashMap
建立在HashMap基础上,jdk1.7时是由多个Segement组成,每个Segment里面类似HashMap结构,默认是有16个Segement,此时最多可以允许16个线程并发操作,Segement指定后不可扩容;jdk1.8时抛弃了分段锁,结构上和HashMap一直,链表加红黑树。
jdk1.7的结构:
每一个Segment都是一个HashEntry<K,V>[] table。
public class ConcurrentHashMap extends AbstractMap implements ConcurrentMap,Serializable{
// 将整个hashmap分成几个小的map,每个segment都是一个锁;与hashtable相比,这么设计的目的是对于put, remove等操作,可以减少并发冲突,对不属于同一个片段的节点可以并发操作,大大提高了性能
final Segment[] segments;
// 本质上Segment类就是一个小的hashmap,里面table数组存储了各个节点的数据,继承了ReentrantLock, 可以作为互拆锁使用
static final class Segment extends ReentrantLock implements Serializable{
transient volatile HashEntry[] table;
transient int count;
}
// 基本节点,存储Key, Value值
static final class HashEntry{
final int hash;
final K key;
volatile V value;
volatile HashEntry next;
}
}
jdk1.8的结构:
取消了segements字段,直接采用 transient volatile HashEntry[] table 保存数据,采用table数组元素作为锁,从而实现了对每一行数据进行加锁,进一步减少并发冲突的概率。
将table数组+单向链表的数据结构,变更为table数组+单向链表+红黑树的结构。单向链表的时间复杂度为O(n),红黑树的时间复杂度为O(logN)。
(2)CopyOnWriteArrayList
CopyOnWriteArrayList是一个线程安全的ArrayList,读操作无锁,add操作时采用拷贝一个新数组来操作,读效率很高,但是有可能有脏读。
原理:
在CopyOnWriteArrayList中,读操作不同步,因为他们在内部数据的快照上工作,所以多个迭代器可以同时遍历而不会相互阻塞。
所有的写操作都是同步的,他们在备份数组的副本上工作,写操作完成后,将原阵列替换为复制的阵列,并释放锁定。支持数据变得易变,所以替换数组是原子操作。
写操作后创建的迭代器将能够看到修改的结构。
写时复制集合返回的迭代器不会抛出ConcurrentModificationException,因为他们在数组的快照上工作,并且无论后续怎么修改,都会像迭代器创建时那样完全返回元素,这里就可能出现脏读现象。
源码解析:
/** The lock protecting all mutators ,执行写时复制操作,需要使用可重入锁加锁*/
final transient ReentrantLock lock =newReentrantLock();
/** The array, accessed only via getArray/setArray. 对象数组,用于存放元素 */
private transient volatile Object[] array;
添加操作:
finalReentrantLock lock = this.lock;
lock.lock();
try{
Object[] elements = getArray();
int len = elements.length;
//拷贝原容器,长度为原容器长度加一
Object[] newElements = Arrays.copyOf(elements, len +1);
//在新副本上执行添加操作
newElements[len] = e;
//将原容器引用指向新副本
setArray(newElements);
return true;
}finally{
//解锁
lock.unlock();
}
(3)CopyOnWriteArraySet
基于CopyOnWriteArrayList实现,但是添加时需要遍历一下元素存在与否,所以效率会低一点;hashSet也是在添加的时候遍历,但是不是顺序遍历,hashSet是基于hashMap实现的,所以根据hashcode确定位置后,直接定位元素。
(4)BlockingQueue
实现类有ArrayBlockingQueue基于数组先进先出,有界;LinkedBlockingQueue基于链表,默认无界,可指定容量;PriorityBlockingQueue 无界。
3、lock
(1)AQS
提供基于Lock接口的并发基础类,主要使用CAS实现。目的是在并发状态下管理被阻塞的线程。提供了一套通用的机制来管理同步状态,阻塞/唤醒线程,管理等待队列等。提供了acquire()、acquireShared()、release()、releaseShared()、hasWaiters()、cas改状态等方法。核心是等待队列(CLH队列)。
(2)CLH队列
双向链表,节点是对线程的包装,分独占和共享两种类型;用前一节点的某一属性表示后一节点的状态;自旋方式不断cas插入包含当前线程的节点到尾部。
(3)Condition
一般配合lock使用,await(),signal(),signalAll()。
(4)ReentrantLock
基于AQS,里面有一个state值,先CAS设置state,如果state!=0,但发现拿锁的是自己则state++,获取失败的加到CLH队列尾,内部自选继续获取锁。
4、Atomic
(1)AtomicInteger
里面由一个volatile修饰的value属性,为了保证值在线程间可见,并发操作采用CAS,可实现原子递增。
源码解析:
其中var表示要被更新的对象,var2是原始值在内存中的偏移地址,通过getIntVolatile方法拿到现在的值var5,现在的var5是一个期望值。compareAndSwapInt是一个native方法,compareAndSwapInt(var1, var2, var5, var5 + var4)是根据var1和var2拿到内存中的值,和期望值var5比较,如果与期望值相等,就将其更新为var5+var4,该方法一直在尝试,直到内存中的值和期望值一样时,才能进行修改,并返回修改前内存的值。
同时还提供了一个compareAndSet方法,当且仅当期望值和内存中的值相等时,才能执行更新操作,可以保证多线程同时修改共享变量时,只有一个线程可以修改成功。
(2) AtomicStampedReference
CAS可能发生ABA问题,即一个变量原来是A,先被修改成B后,又修改回了A,由于CAS操作知识比较当前值和预期值是否一样,在其他线程来看,该变量就好像没有发生过改变。
这种问题可以为数据添加一个时间戳,每次成功修改数据时,不仅修改数据的值,同时要更新时间戳的值,CAS操作时,不仅要比较当前值和预期值,还需要比较当前时间戳和预期时间戳,两者都满足时才能修改成功。
AtomicStampedReference就是这样做的,它不仅维护对象之,还维护一个时间戳,当AtomicStampedReference对应的值被修改时,不仅要更新数据本身,还要更新时间戳(版本号)。
5、线程池
Executor接口,只有一个execute方法;Executors封装了一些常用线程池的方法;ExecutorService接口,继承Executor,新增shutdown,submit,invokeAll方法;ThreadPoolExcutor实现了ExecutorService,可用于创建线程池;Future接口,含cancel,get,isDone方法。
常用线程池,可以通过Executor调用创建,newFixedThreadPool,固定线程数量;newSingleThreadExecutor,单个线程;newCachedThreadPool,可调节线程数量线程池,有核心数量和最大数量;newSingleThreadSceduledExecutor,可以执行定时任务的线程池。
6、同步容器
同步容器主要包含Vector,Stack,HashTable,Collections类中提供静态工厂方法创建的类(Collections.synchronizedXxxx等方法)。
Vector实现了List接口,Vector实际上就是一个数组,和ArrayList类似,但是Vector中的方法都是synchronized方法。
Stack也是一个同步容器,它的方法也用了synchronized进行了同步,它实际上是继承了Cector类。
HashTable实现了Map接口,它和HashMap类似,但是HashTable进行了同步处理。
同步容器的缺陷:同步容器的原理就是在方法上使用synchronized修饰,那么这些方法每次只允许一个线程调用。所以其他视图调用这个方法的线程只能等待,那么性能就比非同步容器差。