java线程(五) concurrent包

1 . 原子类

原子基本数据类型。AtomicInteger、AtomicLong、AtomicBoolean

  • 支持cas设置值,自增等操作。

  • 将基本数据类型的value设置为volatie保证可见性

  • AtomicInteger通过unsafe方法的compareAndSwapInt方法进行设值,AtomicLong使用unsafe的compareAndSwapLong设值,unsafe底层基于cpu的cas指令。

  • 不能解决ABA问题,ABA问题可以通过增加一个版本号解决,可以使用AtomicStampedReference封装基本类型引用。

  • AtomicBoolean将boolean替换成1、0调用compareAndSwapInt设值。

原子数组。AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray

  • 与原子基本数组类型类似,通过unsafe方法进行cas操作,不同的是需要通过数组的起始地址和数组元素的大小计算偏移量,对某个数组位置的值进行修改时,计算对应偏移量,对对应的位置进行cas。

  • 由于是数组类型,无法对每个类型通过volatie修饰保证可见性,因此通过unsafe的getIntVolatile方法,底层插入内存屏障去保证可见性。

  • 通过final修饰数组引用,确保被正确初始化,不被重排序到构造函数外。

  • 传入数组的构造函数方式通过数组复制,避免修改影响原数组。

  • AtomicReferenceArray 可以传入任意类型,通过此类原子修改对应数组位置的对象。

原子更新引用。AtomicReference、AtomicMarkableReference、AtomicStampedReference

  • 通过cas修改引用指向的对象,引用用volatile保证可见性。

  • AtomicMarkableReference 比AtomicReference多封装了一个Pair类,这个类包含引用和一个boolean类型字段,用于标识这个引用改动情况,可以用于避免aba问题。相当于只标识改动过,但是不知道改多少次。

  • AtomicStampedReference 与 AtomicMarkableReference类似封装了pair类,但是包含的是一个引用和一个int类型字段,可以每次更新就修改这个值,避免aba问题。相当于一个版本号,如果从0自增可以得知引用改了多少次。

原子更新引用。AtomicReferenceFieldUpdater、AtomicIntegerFieldUpdater、AtomicLongFieldUpdater

  • AtomicIntegerFieldUpdater 原子更新指定类的指定int类型的字段,通过cas实现。

  • AtomicLongFieldUpdater 创建需要指定更新的类的类型,原子更新指定类的指定long类型的字段,通过cas实现。

  • AtomicReferenceFieldUpdater 原子更新指定类、指定类型的字段,通过cas实现。

  • AtomicIntegerFieldUpdater、AtomicLongFieldUpdater 创建需要指定更新的类的类型,更新值指定修改对象和修改的值。AtomicReferenceFieldUpdater还需要指定修改的字段类型。

2. AbstractQueuedSynchronizer 

简称aqs

作用

  • 多线程访问共享资源的同步器框架,aqs包含了线程入队,线程出队,中断判断等处理。reentrantLock、Semaphore、CountDownLatch等类都是依靠此类实现的。

  • 同步类使用aqs的方式一般是封装内部类继承aqs,实现自定义的同步器,子类只需要实现尝试获取资源和释放资源的实现。

  • 资源获取分为独占方式和共享方式,子类可以只实现独占方式,或者只实现共享方式,也可以都实现。

  • 通过双向链表实现线程入队和出队,FIFO。

主要字段和内部类

  • state 资源数: 共享资源数量,通过控制state的值实现资源获取和释放。比如 reentrantLock state值默认为0, lock时state值加1,unlock减1,限制state值为0时才可获取资源。  

  • node 类: 封装线程的数据结构,包含前后节点引用。

  • head节点、tail节点字段: 通过这两个节点维护双向链表。

  • node类的 waitStatus

  1. CANCELLED(1):表示当前结点已取消调度。当timeout或被中断(响应中断的情况下),会触发变更为此状态,进入该状态后的结点将不会再变化。

  2. SIGNAL(-1):表示后继结点在等待当前结点唤醒。后继结点入队时,会将前继结点的状态更新为SIGNAL。

  3. CONDITION(-2):表示结点等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从等待队列转移到同步队列中,等待获取同步锁。

  4. PROPAGATE(-3):共享模式下,前继结点不仅会唤醒其后继结点,同时也可能会唤醒后继的后继结点。

  5. 0:新结点入队时的默认状态。

  • ConditionObject : 实现condition接口,用于支持类似锁对象的wait、notify、notifyAll方法。ConditionObject实现原理也是通过维护两个节点firstWaiter和lastWaiter实现双向链表,调用await方法的时候,调用release释放资源(即释放锁),并将当前线程插入等待队列队尾阻塞。signal方法则会将等待队列队头的线程唤醒,然后调用enq方法将该线程加入阻塞队列。signalAll则将等待队列的所有线程都唤醒加入到阻塞队列中。

主要方法

  • acquire 获取资源,会调用尝试tryAcquire获取资源方法、addwriter 线程进入队列方法、acquireQueued从队列中获取资源方法。因为会先调用tryAcquire方法,再进队列,如果子类没有控制公平性,就导致获取锁的不公平性。

  • tryAcquire 子类实现,实现通过state值控制共享资源的获取和释放。此方法可以控制公平性和非公平性,比如ReentrantLock的公平锁,会判断等待队列中是否有在当前线程之前的线程,有则不获取资源。

  • addWaiter  将线程封装成node节点 ,通过cas将节点插入到队尾。

  • acquireQueued  队列中的节点获取执行机会的方法,自旋+等待+前继节点唤醒。自旋判断前驱节点是否为头节点,是则尝试获取资源(即头节点的后继节点才可获取资源),获取失败则判断前驱节点是否为signal状态(当前状态的节点释放资源后会唤醒后继节点)。如果不是则判断是否前继节点状态是否大于0,大于0则移动当前节点的前指针到一个正常节点。设置前继节点状态为signal。最后调用park方法进入等待状态(park状态会被中断或者unpark唤醒)。(由于处于自旋状态,所以被唤醒时会再次执行当前方法的流程,被唤醒时会检查是否被中断过,被中断过会设置标记,但是即使被中断也会在获取到资源之后再返回中断状态)。队列中的节点获取到资源后,会将头节点设置为当前节点。

  • release 释放资源,调用tryrelease,调用成功则调用unparkSuccessor唤醒队列中的线程。

  • tryrelease  子类实现,尝试释放资源,修改state值。

  • unparkSuccessor  唤醒头节点的下一个节点,如果被中断,则从尾节点往前找到正常状态的节点唤醒

  • acquireShared 共享资源获取与独享类似,调用tryAcquireShared 尝试获取和doAcquireShared进入队列尝试获取 。因为是多资源,所以可以指定获取的资源数,自旋判断是否为头节点的后继节点,如果是才再次尝试获取资源,如果资源数不足或者当前不是头节点的后继节点则阻塞(进入队列后只有头节点的后继节点才可尝试获取)。因为是多资源,所以如果获取到资源后判断还有资源,会再唤醒后继节点。

  • releaseShared 共享资源释放后同样会唤醒后继节点,并且线程可以不用全部资源都释放再唤醒,可以只要有一个资源释放就唤醒,比如countdownlatch。而ReentrantReadWriteLock的读锁则是某线程占用的资源全部释放才唤醒。唤醒后继节点.

3. LockSupport 阻塞唤醒线程

  • Park 方法通过unsafe的park方法挂起线程,线程进入等待状态waiting。

  • 与wait方法对比 park不会释放锁,wait会释放锁。遇到线程中断请求interrupt,park会响应中断被唤醒,但是不会报中断异常,wait方法会报中断异常。

  • unpark 唤醒挂起的线程。

4. ReentrantLock 

基本知识点

  • 相比synchronized性能差不多(Jdk1.6优化后),但是功能比较丰富

  • 支持公平锁,通过构造函数可以指定是否是公平锁。公平锁性能比较差,因为与操作系统的线程调度不一致。公平锁的原理是通过维护一个线程队列,每次获取锁的顺序和入队顺序一致。非公平锁同一线程多次与其他线程竞争锁,会比较高几率出现连续获取锁,造成饥饿现象。

  • 支持尝试获取锁,失败即返回,以及限时等锁  trylock。

  • 支持可中断锁 lockInterruptibly。

原理

  • 依赖aqs实现线程入队、出队、阻塞、唤醒,共享资源数的控制等。

  • 公平锁和非公平锁原理。ReentrantLock实现了两个继承自aqs的同步器,不公平同步器NonfairSync和公平同步器FairSync,不公平同步器的tryAcquire会直接尝试获取共享资源(即cas修改state的值)。而FairSync会判断当前线程节点是否为头节点的后继节点(如果队列只有当前线程进入过队列,那么head和tail都是当前线程且head.next为tail,如果队列之前已经有其他线程,那么头节点为队列中最后一个获取到资源的线程节点),避免了比先进队列的线程先获取到锁。

  • 可中断锁lockInterruptibly原理。实际上是通过调用aqs的acquireInterruptibly方法,首先尝试获取锁,如果获取失败则进入队列,并通过lockSupport的park方法进入等待状态。如果线程收到中断请求,则会从等待状态被唤醒,acquireInterruptibly会在线程被唤醒后调用线程的interrupted方法判断是否处于中断状态,如果处于中断状态则抛出中断异常。

  • newCondition  实际上就是创建aqs封装的ConditionObject

Synchronized和ReentrantLock的区别

  1. Synchronized是JVM层次的锁实现,ReentrantLock是JDK层次的锁实现;

  2. Synchronized的锁状态是无法在代码中直接判断的,但是ReentrantLock可以通过ReentrantLock#isLocked判断;

  3. Synchronized是非公平锁,ReentrantLock是可以是公平也可以是非公平的;

  4. Synchronized是不可以被中断的,而ReentrantLock#lockInterruptibly方法是可以被中断的;

  5. 在发生异常时Synchronized会自动释放锁(自动添加一个monitorexit),而ReentrantLock需要开发者在finally块中显示释放锁;

  6. ReentrantLock支持尝试获取锁tryLock(),获取失败立即返回,以及支持指定等待时长,更加灵活。

  7. 对于已经在等待队列中的线程,Synchronized是后来的线程先获得锁(线程采用头插法进入队列,并且采用唤醒头节点的形式),而ReentrantLock是先来的线程先获得锁(线程节点通过唤醒后继节点的形式)。

  8. Synchronized 1.6之后会通过锁粗化或锁消除提高性能,即会修改锁范围或者消除掉锁。

5. ReentrantReadWriteLock 

分读写锁,读锁之间不互斥,写锁之间以及写锁和读锁之间互斥。内部封装两个类ReadLock和WriteLock,ReadLock实现aqs的tryAcquireShared实现共享锁,WriteLock实现acquireShared方法实现独享锁。

6. 并发工具类

CountDownLatch

  • 作用: 让一个线程等待多个线程执行结束后再继续执行。线程调用await方法会阻塞,直到所有线程都执行完countDown才被唤醒。

  • 原理: 通过aqs实现,通过实现共享资源获取和共享资源释放方法。await方法会调用doAcquireSharedInterruptibly方法进入队列,并调用tryAcquireShared方法,tryAcquireShared判断只有state等于0才返回1,否则返回-1,如果为-1则当前线程会被阻塞。state默认值由构造函数设置。countDown方法会减state值,并唤醒队列头。

  • 总结即通过await进入阻塞,每次countdown会唤醒该线程,该线程判断state还不是0继续阻塞。

CyclicBarrier

  • 作用:让多个线程之间相互等待。通过await方法表示到达屏障,进行阻塞等待其他线程。构造函数规定屏障拦截的屏障数,并且支持接收一个barrierAction参数,用于指定所有线程都到达屏障时要执行的方法。可以用于多线程处理数据后,barrierAction做汇总。与countdownlatch对比。countdownlatch的计数器只可以用一次,并且计数后需要在主线程await阻塞住。CyclicBarrier可通过reset方法重置计数器,并且可以通过barrierAction指定全到达屏障后的操作,不用阻塞主线程,可各线程全异步阻塞等。CyclicBarrier还可以通过isBroken方法判断阻塞过程是否有被中断,通过getNumberWaiting方法获取已经到达屏障的线程数。

  • 原理:通过reentrantLock和condition实现,每个线程调用await的时候都要通过reentrantLock获取到锁,创建的时候指定线程数,每次await这个数就减1,如果当前调用await的线程数还没减到0就继续调用condition的await方法进入等待,当有一个线程调用await的时候发现线程数减为0了,如果有指定barrierAction,则该线程会执行这个方法,执行结束后,唤醒所有其他的线程。

Semaphore

  • 作用: 可以做流量控制,控制某个资源访问的并发数。构造函数指定允许通过的线程数,acquire表示获取许可,release表示释放许可。使用场景,可以控制同时访问数据库的线程数,数据库被压垮。

  • 原理:通过aqs实现,实现了共享资源方法tryAcquireShared,通过release控制资源数的增加和acquire控制资源减少限制可以通过的资源数,当资源数不足时进入阻塞,资源释放时释放阻塞的线程。

Exchanger

  •  用于两个线程间交换数据,线程调用exchange后会阻塞等待另一个线程也调用exchange,当两个都调用exchange后会将两个线程传入的值返回给对方。遗传算法和交互管道设计中的场景使用较多。exchanger可以多个线程共用,每两个线程成一对的交易。

  • 原理:简单总结存在两种情况,通过unsafe类操作。1. 两个线程直接cas同个内存地址,第一个线程将数据封装后直接存放进去,自旋直到获取到值,然后yeild让出cpu执行时间,第二条线程判断不为空则取出,将该位置cas设置为空,然后将两个线程数据交换。 2.多个线程共用一个exchanger,为避免竞争过于激烈,分为32个位置,计算线程命中哪个位置,命中同个位置的数据相互交换。

7. Future、FutureTask

作用:获取线程执行结果、中断未执行完成的任务、判断任务是否执行完成.

futrueTask实现了RunnableFuture,RunnableFuture继承自Future接口、funnable接口。即futrueTask是future的实现类。

线程池的submit方法就是通过将Callable接口实现类封装到FutureTask类返回,submit方法实际上是把FutureTask作为参数调用execute执行的,FutureTask的run方法实际上是调用Callable的call方法。

调用链:

ThreadPoolExecutor -> submit -> Callable —> FutureTask(封装了Callable,实现了Runnable,run方法调用call方法) -> execute(runnable) 

                                                  -> runnable -> RunnableAdapter(实现了Callable)

原理:

  1. Get获取线程执行结果原理:get方法会去获取执行结果成员变量是否有值,没有值就通过LockSupport.park阻塞(broker为当前futureTask对象)。futrueTask的run方法在执行结束后会把结果设置到成员变量,然后通过LockSupport.unpark遍历唤醒因为get方法阻塞的线程。

  2. 中断未执行完成的任务:mayInterruptIfRunning参数用于标示是否允许取消正在执行的任务。先把执行状态修改成中断状态,调用线程的interrupt方法执行中断(还是不能把执行一半的中断),唤醒所有等待结果中的线程。

  3. isDone判断任务是否执行完成:通过一个变量判断记录执行状态,刚创建为new状态,执行完成或异常都会改成别的状态。

执行失败会将state设置为异常状态,唤醒等待结果线程,get方法调用获取到状态是异常状态,会抛出ExecutionException异常。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值