JUC
java.util .concurrent
是Java自带的一个为了处理多线程高并发的包
synchronizedVS
AtomicLongVS
LongAdder
在对某一个数做自增时,哪一种方式更快?
效率(一般情况下,实际情况以测试为准):
adder
> atomic
> sync
synchronized
底层是使用了重量级锁(在于并发量的多少,有一个锁升级的过程),重量级锁是由OS帮忙实现的,上锁解锁的过程比较繁琐。
AtomicLong
原子类,通过CPU原语保证原子性,底层是CAS的实现。
LongAdder
经典的空间换时间的做法,若使用CAS的做法,若并发数量比较高的时候,自旋次数增多,CPU资源浪费严重,效率降低,LongAdder的做法就是将这么多个并发分布到多个值上去(一个Cells数组),这样再对每一个cell做CAS操作,最后需要结果时,再将这几个数组相加起来即可。
ReentrantLockVS
sychronized
虽然说ReentrantLock的称为可重入锁,但是sychronized同样也是可重入的。
不同点:
ReentrantLock
的使用更加灵活、功能更加强大(下文会举例说明)
ReentrantLock更多的功能
tryLock()
给定一个时间试图去获取锁,返回一个boolean
值,表明是否获取到锁
lockInterruptibly()
可以被打断的加锁方式,一般使用lock()
方式进行加锁时,必须在获取线程之后才能进行interrupted()
打断;而使用此方法,即使没有获取到锁也可以被interrupted()
打断。
公平锁new ReentrantLock(true)
公平锁,即每个线程获取该锁都是相对公平的,即新的线程来获取锁时,会先检查队列里有没有等待的线程,若有,则进入队列中等待,若没有,则直接参与锁的竞争;
而非公平锁,即新的线程来获取锁时,直接参与锁的竞争。
CountDownLatch
一个倒计时的秒表,即初始化时,设定一个数,当有线程调用了
await()
方法时,就会一直等待着,直到CountDownLatch
内部的数减少至0(调用countDown()
方法执行),即可打破等待状态开始执行。
应用场景:
实现串行化执行,等待一些线程执行完毕后,再去执行一些代码逻辑;
例如:《英雄联盟》游戏10个人都加载完毕了,主地图界面线程也就开始执行了。
CyclicBarrier
相当于一个闸门,初始化的时候,设置一个阈值,当调用
await()
方法的线程数达到指定阈值时,那些方法即可全部进行执行,也可以指定一个action()
即执行这些线程之前需要先执行一个指定线程;CycliBarrier
可以自动重置,循环使用,当“放闸”之后,自动恢复初始状态。
应用场景:
实现同步开始执行,当等待的线程数达到了阈值,则同时开始执行;
多线程并行计算
Phraser
相当于多道闸门,每个阶段都做一些事情,可以令一些线程在某些阶段被阻拦,以至于不会继续向前执行
phraser
内的代码逻辑。
应用场景:
遗传算法的实现
ReadWriteLock
读写锁,即有读锁与写锁;与
MySQL
的读写锁机制类似。
- 读锁:也称共享锁,在加了读锁的过程中,其他的读线程也可以来读取该值
- 写锁:也称排他锁,在写数据的过程中,其他的读写现场均不能来操作该值
Semaphore
信号灯
- 初始设定一个值
- 每个线程调用
acquire()
,获取许可,才能往下执行,若获取许可成功,则semaphore
内的值-1
,若semaphore
内的值为0,则阻塞; - 调用
release()
方法,则是释放许可,需要获取许可之后才能调用,semaphore
内的值+1
,若其他有在等待获取许可的线程,则此时即可去抢夺此许可机会
应用场景
限流,限制最多几个线程同时执行
Exchanger
交换器,用于线程之间交换数据;
A线程调用
exchange()
方法把值传到Exchanger
中去,并进入阻塞状态;B线程调用exchange()
方法将值传到Exchanger
中去,同时Exchanger
内部将两个值进行交换,并返回;A、B线程获取数据,解除阻塞状态,继续执行
LockSupport
锁的支持,可以灵活地使线程停下来
park()
与继续执行unpark()
unpark()
方法可以先于park()
方法调用