线程:
设置线程名称方便排错,对线程中断作出恰当的响应。
线程副本:
ThreadLocal它是local variable(线程局部变量)。它的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本。
线程资源同步机制:
JVM把对于working memory的操作分为了use、assign、load、store、lock、unlock,对于main memory的操作分为了read、write、lock、unlock(有线程执行引擎发出)。
lock/unlock机制原理与synchronized相同(一个Lock可以对应多个Condition,而synchronized把 Lock和Condition合并了,一个synchronized Lock只对应一个Condition), 都用于保证某段代码执行的原子性。由于锁会阻塞其他线程同样需要的锁的部分执行,所以使用锁机制时要避免死锁。
注意: 静态方法上加synchronized, 为同步整个类对象。
ReentrantLock
基于AbstractQueuedSynchronizer的实现,AbstractQueuedSynchronizer为基于整数状态值实现资 源的并发控制访问提供了很好的支持。
ReentrantLock(boolean fair)创建一个FairSync或NonfairSync的对象实例,NonfairSync或FairSync继承自内部Sync,而Sync继承 自AbstractQueuedSynchronizer。
lock()调用创建的sync对象实例的lock(),会调用LockSupport.park(this), 实际使用的是Unsafe.getUnsafe().park(false, 0L)。
unlock()调用创建的sync对象实例的release(int arg),会调用LockSupport.unpark(s.thread),实际使用的是Unsafe.getUnsafe().unpark(thread)。
ReentrantReadWriteLock
没有继承于ReentrantLock,提供读锁、写锁。基于AbstractQueuedSynchronizer来实现,自行实现判断是否可获取读锁或写锁。
读锁调用lock方法时,如果没有线程持有写锁,就可获得读锁而无需阻塞等待。
写锁调用lock方法时,如果没有线程持有读锁或写锁,就可获得写锁而无需阻塞等待,否则就需要阻塞等待,因此写操作影响整体性能。
读写锁分离,在读多写少的场景下可大幅度提升性能。
ReentrantReadWriteLock())创建ReentrantReadWriteLock.ReadLock、ReentrantReadWriteLock.WriteLock、FairSync或NonfairSync这三个的对象实例。都是静态内部类。
volatile的机制仅用于控制线程中对象的可见性(不会将其从main memory复制到work memory中,而是直接在main memory中进行操作), 不能保证操作的原子性。
atomic操作类(基于CAS的数据结构),无阻塞的、CAS由硬件提供原子操作指令实现的(CPU原语)。
操作都基于compareAndSet(int expect, int update),compareAndSet调用UnSafe的compareAndSwapXXX,因方法为native、基于CPU的CAS原语来实现。
CAS简单地说就是由CPU比较内存位置上的值是否为当前值,如是则设置为next,否则返回false,因此CAS代码片段要在一个无限循环中执行,这样可保证并发的健壮性。(LockFree算法)
线程交互机制:
JVM提供基于Object的wait/notify/notifyAll方式。
jdk 5.0提供的并发包:Condition的await/signal/singalAll、Semphore的acquire/release、CountDownLatch的await/countDown、CyclicBarrier的await。
CountDownLatch:用于控制多个线程同时开始(当你启动了一个线程,你需要等它执行结束,或当你启动很多线程,你需要这些线程等到通知后才真正开始)。
CyclicBarrier:如传入Runnable的对象。await达到了设定数量后,会首先执行此Runnable的对象,才继续往下执行,(来实现并发性能测试的聚合点)。
基于ReentrantLock、Condition。
Semaphore:用于控制某资源同时被访问的个数。
ReentrantLock(boolean fair)创建一个FairSync或NonfairSync的对象实例,NonfairSync或FairSync继承自内部Sync,而Sync继承 自AbstractQueuedSynchronizer。
tryAcquire(long timeout, TimeUnit unit)调用创建的sync对象实例的tryAcquireSharedNanos(int arg, long nanosTimeout)。
release()调用创建的sync对象实例的releaseShared(int arg)。
线程池:
ThreadPoolExecutor提供线程池服务。
构造函数:ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
Future<?> submit(task),task封装为FutureTask, 再通过execute(Runnable)负责将封装后的task放入线程中执行。
ThreadPoolExecutor的4种RejectedExecutionHandle实现:AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy、DiscardPolicy。
高效地支持并发的ThreadPoolExecutor,必须合理分配corePoolSize、maximumPoolSize、workQueue、handler。
以下工作队列:
高性能(任务缓冲队列大小为1,直接交给线程执行):SynchronousQueue。
缓冲执行(任务缓冲队列大小为corePoolSize):ArrayBlockingQueue、LinkedBlockingQueue。
任务的提交与执行:
为了方便并发执行任务,Executor是执行任务的实现。
Executors是Executor的工厂类。
newFixedThreadPool(int),corePoolSize数量且线程启动后一直运行(与maximumPoolSize数量一样),keepAliveTime为0,执行的task超出传入的大小值后,将放入工作队列LinkedBlockingQueue中,当执行的task超出工作队列大小时抛出RejectedExecutionException。
newSingleThreadExecutor(),corePoolSize与maximumPoolSize都为1的newFixedThreadPool。
newCachedThreadPool(),corePoolSize为0,maximumPoolSize为Integer.MAX_VALUE,keepAliveTime为60L秒(启动线程存活时间),工作队列SynchronousQueue, 在使用时执行的task会复用线程或启动新线程来执行。
newScheduledThreadPool(int),为ScheduledThreadPoolExecutor,corePoolSize为传入参数,maximumPoolSize为Integer.MAX_VALUE,keepAliveTime为0纳秒,工作队列DelayedWorkQueue。
Future是提交者和执行者之间的通讯实现,包括get(阻塞至任务完成),cancel,get(timeout)(等待一段时间)等。
Future也用于异步变同步的场景。可用于异步获取执行结果或取消执行任务的场景,通过传入Runnable与Callable给FutrueTask。
FutrueTask(task)
FutrueTask构造函数,创建一个内部类Sync的对象实例。Sync基于AbstractQueuedSynchronizer来实现,Sync(Callable<V> callable),如task是Runnable,需通过RunnableAdapter适配成Callable实现类。
run()调用Sync对象的innerRun方法,首先基于CAS将状态READY设置为RUNNING,如果设置失败则直接返回。
获取当前线程, 判断当前state是否为RUNNING,如果是则callable.call(),并把使用set保存执行结果,set调用的是Sync对象的innerSet(使用lock-free算法),否则就调用releaseShared(0)。
get(long timeout, TimeUnit unit)调用创建的sync对象实例的innerGet(long nanosTimeout)方法。
cancel(boolean mayInterruptIfRunning)调用创建的sync对象实例的innerCancel(boolean mayInterruptIfRunning)。
任务定时:
使用ScheduledExecutorService,不建议你再使用java.util.Timer。
可用于异步操作时需要超时回调的场景。
Timer与ScheduledThreadPoolExecutor的三大区别:
1)Timer只能单线程,一旦task执行缓慢,就会导致其他task执行推迟,ScheduledThreadPoolExecutor可执行控制线程数量。
2)当Timer抛出RuntimeException异常时,会导致Timer中所有task都不再执行。
3)ScheduledThreadPoolExecutor可执行Callable的task,从而在执行完毕后得到执行结果。
并发三大定律:Amdahl、Gustafson、Sun-Ni
并发集合分析:
1。ConcurrentHashMap
采用Segment对象数组,Segment继承ReetrantLock, Segment采用HashEntry对象数组(采用key、hash、next、value的HashEntry<K,V>对象)、线程安全。
多了一个concurrencyLevel属性,以及Segment具有threshold、loadFactor属性,指定大小为cap的HashEntry对象数组 。(threshold = (int)(newTable.length * loadFactor))
基于concurrencyLevel划分出了多个Segment来对于key-value进行存储。(默认分为16段,分别持有各自的锁,锁仅用于put和remove等改变集合对象的操作,基于volite及HashEntry链表的不变性实现读取的不加锁)
基于key hash寻找Segment对象存放到数组的位置,从而避免每次操作都得锁住整个数组。
put操作根据key hash找到对应数组中Segment对象,可能要对找到后的Segment对象中的HashEntry对象数组进行扩容(rehash方法,计算公式为oldCapacity<<1), 设置threshold值, 最后需要对扩大容量后对象数组重新hash并复制对象到新的数组中(p.hash & sizeMask),最后设置threshold值。
keySet.iterator()后, 通过遍历每个分段中的HashEntry对象数组,完成集合中所有对象的遍历,在遍历过程中也是不加锁的, 因此不会抛出ConcurrentModificationException。
size操作,由于没有一个全局锁,所以这样的方法比较复杂。在不加锁的情况下遍历所有的Segment,读取Segment的count, modCount, 完毕后再遍历所有的Segment,看modCount是否有改变, 如有改变则再尝试一次以上动作。如果还有问题,则遍历Segment加锁、然后遍历读取Segment的count,遍历Segment释放锁。
2。CopyOnWriteArrayList
基于保证增加与删除元素的互斥,读操作无锁、保证了很高的读性能但可能会出现读脏数据、线程安全、。
构造函数与ArrayList不同, 创建一个大小为0的数组。
add(E)使用ReentrantLock保证线程安全,每次都创建一个新的Object数组,此数组大小为当前数组大小加1,并将之前数组的内容复制到新数组中,新增加的对象放入数组末尾,最后把引用切换到新数组。
remove(E)
get(int)直接获取当前数组对应位置的元素, 没有加锁保护, 因此可能会出现读脏数据。
iterator()创建一个新的COWIterator对象实例,并保存一个当前数组的快照。
3。CopyOnWriteArraySet
基于CopyOnWriteArrayList,不同的是使用addIfAbsent、addAllAbsent,需遍历当前Object数组, 如当前Object数组已有了当前元素,则直接返回,否则就放入Object数组尾部并返回。
4。ArrayBlockingQueue
基于固定大小数组、ReentrantLock以及Condtion实现的阻塞先进先出队列(可指定时间的阻塞读写)、线程安全。
ArrayBlockingQueue(int capacity, boolean fair),没有默认构造,capacity为数组大小,fair用于初始化锁,两个锁Condition、notEmpty、notFull。(上述属性都为final)
offer(E e, long timeout, TimeUnit unit),用于将元素插入数组尾部,如数组已满则等待,直到以下三种情况才继续:被唤醒、到达指定时间、当前线程中断。(首先将指定时间转化为纳秒,然后加锁,如数组未满则将元素插入数组尾部,如已满且已超过指定时间则返回false,如未超时调用notFull的awaitNanos进行等待,线程被中断则抛出InterruptedException异常,如为被唤醒或超时则继续重复以上动作--lockfree算法)
poll(long timeout, TimeUnit unit), notEmpty的awaitNanos进行等待。
4。LinkedBlockingQueue
基于链表、读只在队头(take和poll)、写只在队尾(put和offer),因此采用两把锁,避免了读写竞争。在高并发读写都多的情况下性能比ArrayBlockingQueue好很多, 遍历及删除元素则锁住两把锁。
并发小结:
高性能并发类库:基于CAS、 读写锁分离、 拆分锁粒度、volatile、AbstractQueuedSynchronizer实现, 来尽量减少高并发时的竞争。
LockFree算法,不需要加锁。通常都是三个部分组成:循环、CAS (CompareAndSet)、回退。(可以使用LockFree算法,来实现乐观锁)
学习参考资料:
《分布式Java应用:基础与实践》、《Java并发编程实践》、温少的《Java并发程序设计教程》、The Java™ Tutorials、jdk7类库源码。
补充了《分布式Java应用:基础与实践》一些代码样例:
AtomicDemo源码
FutureTaskDemo源码
ThreadPoolExecutorDemo源码
ReadWriteLockDemo源码