Java并发学习笔记

本文深入探讨Java并发编程的关键概念和技术,包括线程管理、锁机制、原子操作、线程池设计、Future模式、读写锁的应用以及各种并发集合的特点。通过对核心API的剖析和示例代码解读,帮助读者理解如何构建高效稳定的并发应用程序。
摘要由CSDN通过智能技术生成


线程:
设置线程名称方便排错,对线程中断作出恰当的响应。

线程副本:
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源码

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值