高级篇
- 锁
- >定义
- >特性
- >种类
- >实现
- > 锁相关类
- Lock接口为中心,最有名的是ReentrantLock。
- ThreadLocal
- >ThreadLocalMap
- >ThreadLocalRandom
- J.U.C
- > 线程同步类
- 主要代表CountDownLatch、Semaphore、CyclicBarrier等
- > 并发集合类
- ConcurrentHashMap/ConcurrentSkipListMap/CopyOnWriteArrayList/BlockingQueue等
- > 线程管理类
- Excutors静态工厂或者使用ThreadPoolExcutor等。换可以通过ScheduledExcutorService来执行定时任务。
- 线程池
- 原子变量
线程主要通过共享对字段和对象引用字段的访问进行通信这种通信形式非常有效,但是可能出现两种错误:线程干扰和内存一致性错误。防止这些错误所需要的工具是同步。又会带来活锁和饥饿等问题。
- 线程干扰:
- 内存一致性:
- 同步方法:
- 隐式锁(内置锁)和同步:synchronized两个作用:强制对对象状态的独占访问和建立对可见性至关重要的happens-before关系。
- 原子访问
锁
- 定义:锁是与同步块类似的线程同步机制,只不过锁可能比Java的同步块更复杂。锁(和其他更高级的同步机制)是使用同步块创建的,所以我们不能完全不使用synchronized关键字
- 锁的两种特性:互斥性和不可见性。
2.1 引申内置锁synchronized的可重入性:
内置锁synchronized虽然可以防止并发但是导致的结果是性能会比之前下降很多。因为在加锁的类,对象,代码块,在同一个时间只能由一个线程持有,其他都得阻塞等待。
一种解决办法就是拆分代码块,只针对需要共享变量的地方进行同步锁。
重入性意味着获取锁的操作的粒度是线程,而不是调用。重入的一种实现方法,为每个锁关联一个获取计数值和一个所有者线程。当计数值为0时候,这个锁就被认为是没有被任何线程持有,当线程请求一个未被持有的锁时候,JVM记下锁的持有者,并将获取值设为1.如果同一个线程再次获取这个锁,计数值将递增,当线程退出同步代码块,计数器会相应的减少。当计数值为0,锁被释放。
当前该线程是可以先执行outer然后执行inner方法,
如果是不可重入的话,那么他不能再进入inner方法
public class Reentrant{
public synchronized outer(){
for(int i=0;i<100;i++){
inner(i);
}
}
public synchronized inner(int i){
//do something
}
}
- 锁的种类:
- 3.1、自旋锁
将条件值放到while循环里面,eg:
while(条件不满足){
threadObject.wait();
}
备注:如果不希望一直判断,可以加上重试次数
当如果递归使用的时候,则自己持有锁就会一直等待发生死锁。则需要可重入性,可以通过加上标记次数0和1来控制实现可重入性。
- 3.2、公平锁与非公平锁
由于synchronized同步代码块每次释放或者锁的unlock释放或者notify或者notifyAll都会引起线程争抢锁,假设如果一直出现争抢,极小概率会发生饥饿现象(某个线程一直抢不到)。我们可以将线程放到队列里面,每次获取队列中的第一个线程,执行完毕,将该线程从队列中删除。这样实现了如同排队买票一样,虽然实现了公平,但是也牺牲了线程优先级。
ReentrantLock提供了公平和非公平锁的实现。
公平锁:ReentrantLock pairLock = new ReentrantLock(true)
非公平锁:ReentrantLock nopairLock=new ReentrantLock(false).如果构造函数不传递参数,则默认是非公平锁。- 3.3、读写锁(共享锁和独占锁)
解决只有一个写-多个读的情况,例如volatile一样适用的场景。并为了防止死锁要保证锁的可重入性。
读权限:没有线程正在写,或者请求写的权限
写权限:如果没有线程正在读或者正在写。
独占锁(悲观锁):
保证任何时候都只有一个线程能得到锁,ReentrantLock就是以独占方式实现的。
共享锁:
共享锁(乐观锁)则可以同时由多个线程持有,例如ReadWriteLock读写锁,它允许一个资源可以被多线程同时进行操作。
- 3.4、乐观锁与悲观锁
数据库和并发包锁里的思想
悲观锁–强制锁定,同一时刻,同一个数据只有一个处理
乐观锁–添加版本号,通过每次最大版本号控制
- 锁实现的方式:
- 4.1、利用并发包中的锁类
- 4.1.1、LockSupport工具类
作用:挂起和唤醒线程,该工具类是创建锁和其他同步类的基础。LockSupport类与每个使用它的线程都会关联一个许可证,在默认情况下调用LockSupport类的方法的线程是不持许可证的。LockSupport是用Unsafe类实现的。
主要函数:
void park()方法
如果调用park方法的线程已经拿到了与LockSupport关联的许可证,则调用LockSupport.park()会马上返回,否则调用线程会被禁止参与线程的调度,也就是会被阻塞挂起。
void unpark()方法
唤醒park后的线程,继续执行完毕。相当于之前park方法说的关联许可证。
void parkNanos(long nanos)方法
与park方法类似,如果已经拿到许可证,则调用后会立马返回,但是如果没有拿到许可证,则调用线程挂起nanos时间后修改为自动返回。
void park(Object blocker)方法
当线程在没有许可证的情况下被调用park方法阻塞挂起,则blockers对象会被记录到该线程内部。通过诊断工具调用getBlocker(Thread)方法来获取broker对象。这样在线程堆栈排查问题时候可以知道哪个类被阻塞了。
比如使用park方法通过jstack pid命令可以看到线程堆栈的结果为:
如果修改为park(this)则可以看到结果为
另外的两个方法void parkNanos(Object blocker,long nanos)相比park(Object blocker)方法多了个超时时间。
void parkUntil(Object blockers,long deadline)这个方法和上面的类似,前者是指定时间点后者是从当前算等待nanos秒时间。Lock是JUC包的顶层接口,实现逻辑利用了volatile的可见性。
备注:如果使用lock()方法,一定要在finally中使用unlock()方法,防止异常或者中途线程中断,锁一直无法释放问题。
比较出名的AQS-----AbstractQueuedSynchronizer
AQS是抽象类,内置自旋锁实现的同步队列,封装入队和出队的操作。提供独占、共享、中断等特性的方法。
地位:它是实现同步器的基础组件,并发包中锁的底层就是使用AQS实现的。AQS维持了一个单一的状态信息state,可以通过getState,setState,compareAndSetState来修改值。state的其他作用:
比如可重入锁ReentrantLock,定义state为0时候可以获得资源并置为1.若已经获得资源,state不断加1,在释放资源时候state减1直至为0。由于该锁是独占锁,同一个时刻只有一个线程可以获取该锁。
CountDownLatch,初始化时候定义了资源总量state=count,countDown()不断将state减1,当state=0才能获得锁,释放后state=0.所有线程调用await都不会等待。所以CountDownLacht是一次性的,用完后如果再想用就只能重建一个。
CountDownLatch与join方法的区别:一个区别是调用一个子线程的join()方法后,该线程会一直被阻塞直到子线程运行完毕,而CountDownLach则使用计数器来允许子线程运行完毕或者在运行中递减技术。也就是CountDownLach可以在子线程运行的任何时候让await方法返回而不一定必须等到线程结束。另外,使用线程池来管理线程时候一般都是直接添加Runnable到线程池,这时候就没有办法再调用线程join方法,就是说countDownLatch相比join方法让我们对线程同步有更灵活的控制。小结:首先在初始化CountDownLatch时候设置状态值(计数器值),当多个线程调用counDown方法时候实际是原子性递减AQS的状态值。当线程调用await方法后当前线程会被放入AQS的阻塞队列等待计数器为0再返回,其他线程调用countDown方法让计数器值递减1,当计数器值变为0时候,当前线程还要调用AQS的doReleaseShared方法来激活由于调用await方法而被阻塞的线程。
如果希望循环使用,推荐使用ReentrantLock实现的CyclicBarrier。
循环屏障详细说明
Semphore与CountDownLach有些不同,它内部的计数器是递增的,并且在一开始初始化Semaphore时候可以指定一个初始值,但是并不需要知道需要同步的线程个数,而是在需要同步的地方调用acquire方法时候指定需要同步的线程个数。
实现原理:定义了资源总量state=permits,当state>0时候获得锁,并将state减1,当state=0时候只能等待其他线程释放锁,当释放锁时候state加1,其他等待线程又能获得这个锁。当Semphore的permits定义为1就是互斥锁,当permits>1时候就是共享锁
可以看下代码实例如下:
package test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
/**
* @author xcxyz
* @create 2019-04-19 7:05
**/
public class Demo {
//创建一个信号量,当大于0时候获取锁,获取锁state-1,当大于1可重入锁,等于1互斥锁
private static Semaphore semaphore = new Semaphore(0);
//开启线程测试
public static void main(String args[]){
ExecutorService executorService = Executors.newFixedThreadPool(2);
///添加线程A到线程池
executorService.submit(new Runnable() {
@Override
public void run() {
try{
System.out.println(Thread.currentThread()+"over");
semaphore.release();
}catch (Exception e){
e.printStackTrace();
}
}
});
executorService.submit(new Runnable() {
@Override
public void run() {
try{
System.out.println(Thread.currentThread()+"over");
semaphore.release();
}catch (Exception e){
e.printStackTrace();
}
}
});
try {
semaphore.acquire(2);
System.out.println("all over");
//关闭线程池
executorService.shutdown();
}catch (Exception e){
e.printStackTrace();
}
}
}
构造函数入参为0,说明当前信号量计数器值为0,然后main函数向线程池添加两个任务,在每个线程内部调用信号量的release方法,这相当于计数器值递增1.最后acquire方法,相当于执行这里的线程会一直阻塞,直到信号量变为2,线程都执行完毕,才可以返回。而这一点也在某种程度上可以先执行其他线程,然后聚合线程和循环屏障类似。
JDK8中的新锁StampedLock,改进了读写锁ReentrantReadWriteLock。
StampedLock:
三种模式读写控制:调用获取锁的系列函数时候,返回一个long型的变量,我们称之为戳记(stamp),这个戳记代表了锁的状态。其中try系列获取锁的函数,当获取锁失败会返回为0的值,当调用释放锁和转换锁的方法时候需要传入获取锁返回的stamp值。
- 写锁writeLock:
排它锁或者独占锁。类似于ReentrantReadWriteLock的写锁(但是stampedLock的写锁是不可重入的)
- 悲观读锁readLock:
共享锁,在没有线程获取独占写锁的情况下,多个线程可以同时获取该锁。也类似于ReentrantReadWriteLock的读锁(但是区别是这里不可重入)
- 乐观读锁tryOptimisticLock:
相对于悲观锁,在操作数据前没有通过CAS设置锁的状态,仅仅通过位运算测试。如果当前没有线程持有写锁,则简单返回非0的stamp的版本信息。获取该stamp后再具体操作数据前还需要验证validate方法验证是否在获取期间被其他线程持有写锁造成不可用。实现原理:如同我们数据库中的可重复读,但是效率会比CAS操作和其他加锁操作高的多。适用于读多写少的场景。
特点:不可重入锁,其他与ReentrantReadWriteLock类似,但是在多线程多读的情况下 会更高的性能。
该锁的实现是自己内部维护了一个双向阻塞队列,并不是直接实现Lock或者ReadWriteLock。
ReentrantReadWriteLock:
满足写少读多的场景:
读写锁内部维护了ReadLock和一个WriteLock,它们依赖Sync实现具体功能。而Sync继承自AQS,并且也提供了公平和非公平的实现。因为AQS只维护了一个state状态,而ReentrantReadWriteLock则需要维护读状态和写状态 。一个state值如何维护2种状态,则可以使用高16位表示读状态,也就是获取到读锁的次数,低16位表示获取到写锁的线程可重入次数
/*
* Read vs write count extraction constants and functions.
* Lock state is logically divided into two unsigned shorts:
* The lower one representing the exclusive (writer) lock hold count,
* and the upper the shared (reader) hold count.
*/
static final int SHARED_SHIFT = 16;
共享锁(读锁)状态单位值65536
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
共享锁线程数最大个数65535
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
排他锁(写锁)掩码,二进制,15个1
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
返回读锁线程数
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
返回写锁可重入个数
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
AQS–内部类ConditionObject用来结合锁实现线程同步。ConditionObject可以直接访问AQS对象内部的变量,比如state状态值和AQS队列。ConditionObject是条件变量,每个条件变量对应一个条件队列(单向链表队列),其用来存放调用条件变量的await方法后被阻塞的线程。
一个锁对应一个AQS阻塞队列,对应多个条件变量,每个条件变量都有自己的一个条件队列>> >基于AQS实现自定义的同步器
- 4.2、利用同步代码块
同步代码块一般使用java的synchronized关键字来实现。有两种方式对方法进行加锁操作。
- 第一:在方法签名处加synchronized关键字:
public class MyClass {
实例方法
public synchronized void log1(String msg1, String msg2){
log.writeln(msg1);
log.writeln(msg2);
}
public void log2(String msg1, String msg2){
实例方法的代码块里面
synchronized(this){
log.writeln(msg1);
log.writeln(msg2);
}
}
}
public class MyClass {
静态方法
public static synchronized void log1(String msg1, String msg2){
log.writeln(msg1);
log.writeln(msg2);
}
public static void log2(String msg1, String msg2){
静态方法的代码块里面
synchronized(MyClass.class){
log.writeln(msg1);
log.writeln(msg2);
}
}
}
- 第二:使用synchronized(对象或类)进行同步。这里的原则是锁的范围尽可能小,时间尽可能短。
JVM对synchronized的优化主要在于对monitor的加锁和解锁上。JDK6不断优化提供三种锁的实现。包括偏向锁、轻量级锁、重量级锁,还提供自动的升级和降级机制。JVM就是利用CAS在对象头上设置线程ID,表示这个对象偏向于当前线程,这就是偏向锁。根据该线程id判断是否需要重复获取锁,如果一致不需要重新获取锁。如果出现锁的竞争,那么偏向锁会被撤销并升级为轻量级锁,竞争的非常严重会升级为重量级锁。而偏向锁可降低无竞争开销,它不是互斥锁,不存在线程竞争的情况,省去再次同步判断的步骤,提升了性能。
线程同步:
同步是什么原子性是指不可分割的一系列操作指令,在执行完毕前不会被任何其他中断,要不全部执行,要不全部不执行。如果每个线程的修改都是原子操作,就不存在线程同步问题。
比如i++操作。
ILoad—>IINC---->ISTROE。另一方面,更加复杂的CAS操作却具有原子性。
实现线程的同步方式:比如同步方法、锁、阻塞队列等。
线程池:
主要解决的问题:
1、当执行大量异步任务时候线程池能够提供较好的性能,在不使用线程池时候,每当需要执行异步任务时候直接new一个线程来运行。而线程的创建和销毁是需要开销的。线程池里面的线程是可复用的,不需要每次执行异步任务时候都重新创建和销毁线程。
2、线程池提供了一种资源限制和管理的手段,比如可以限制线程的个数,动态新增线程等。
线程池的好处作用:
1、利用线程池管理并复用线程,控制最大的并发数等。
2、实现任务线程队列缓存策略和拒绝机制
3、实现某些与时间相关的功能,如定时执行,周期执行等。
4、隔离线程环境。比如交易和搜索都在同一台服务器上,那么分别开启两个线程池,这样可以根据不同场景配置不同的策略。
Exeutor类图:
创建线程的步骤:
1.ThreadPoolExcutor、ThreadFactory和RejectedExcutionHandler。
ThreadPoolExcutor的excute和addWorker两个核心方法。
Executors的核心五个方法:newWorkStealingPool:JDK8引入,创建持有足够线程的线程池支持给定的并行度,并通过使用多个队列来减少竞争,此构造方法把CPU数量设置为默认的并行度。
返回对象是ForkJoinPool(JDK7引入)newCachedThreadPool: 创建一个按需创建线程的线程池,初始线程个数为0,线程个数为Integer.MAX_VALUE,并且阻塞队列为同步队列。KeepAliveTime=60说明只要当前线程在60s内空闲则回收。这个类型的特殊之处在于加入同步队列的任务会被马上执行,同步队列里面最多只有一个任务。
public static ExecutorService newCachedThreadPool(){
return new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.SECONDS,new SynchronousQueue);
}
//使用自己定义的线程工厂
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory){
return new ThreadPoolExecutor(0,Integer,MAX_VALUE,60L,TImeUnit,SECONDS,new SynchronousQueue(),threadFactory)
}
类图中mainLock是独占锁,用来控制Worker线程操作的原子性,termination是该锁对应的条件队列,在线程调用awaitTermination时候用来存放阻塞的线程。
work继承AQS和Runnable接口,是具体承载任务的对象。
newScheduledThreadPool:
ScheduledThreadPoolExecutor继承了ThreadPoolExecutor并实现了ScheduledExecutorService接口,线程池队列是DelayedWorkQueue,其和DelayedQueue类似是一个延迟队列。
ScheduledFutureTask是具有返回值的任务,继承自FutureTask。FutureTask的内部有一个变量state用来表示任务的状态,一开始状态为NEW,所有状态为:
newSingleThreadExcutor:
newFixedThreadPool:
使用无界队列,如果瞬间请求非常大,会有OOM的风险。除了newWorkStealingPool,其他四个线程池创建方法都存在资源耗尽的风险。
ThreadPoolExecutor的实现原理
ThreadPoolExecutor提供了四个公开的内部静态类:
AbortPolicy:丢弃任务并抛出RejectExcutionException异常
DiscardPolicy:丢弃任务,但是不抛出异常,这是不推荐的做法
DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前的任务加入队列中
CallerRunsPolicy:调用任务的run()方法绕过线程池直接执行
线程池使用的注意事项:
1、合理设置各类参数,应该根据实际业务场景来设置合理的工作线程数
2、线程资源必须通过线程池来提供,不允许在应用中自信显示创建线程
3、创建线程或线程池时候请指定有意义的线程名称,方便出错时候回溯
线程池不允许使用Excutors,而是通过ThreadPoolExcutor的方式创建,这样处理方式能够更加明确线程池运行规则,规避资源耗尽的风险。
ThreadLocal初衷是在线程并发时候,解决变量共享问题,但是由于过度设计,比如弱引用和哈西碰撞导致理解难度大,使用成本高,反而成为故障高发点。容易出现内存泄漏,脏数据,共享对象更新的等问题。
引用类型:
强引用:强关联 Ha ha =new Ha();这种引用关系存在就不会被回收
软引用:二次收集的时候如果收集后内存还不够则OOM
弱引用:每次收集都有它
虚引用:作用只是在回收的时候得到一个系统通知
System.gc()方法建议垃圾收集器尽快进行垃圾收集,具体何时执行仍由JVM来判断。System.runFinalization()方法的作用是强制调用已经失去对象的finalize()方法。
多线程访问同一个共享变量如同上篇文章说的,容易出现并发问题(尤其是多线程对共享变量进行写入操作)。所以为了保证线程安全,使用者可以访问共享变量时候进行适当的同步。加锁,一写多读场景下volatile,信号量同步等,那么每个线程创建一个变量后,每个线程访问是自己的线程变量还有其他方法就是threadLocal.他提供了线程本地变量,也就是如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。当多个线程操作这个变量时候,实际操作的是自己本地内存里面的变量,从而避免了线程安全问题。
定义:
将这个对象设置为共享变量,统一设置初始值,但是每个线程对这个值的修改都是互相独立的。这个对象就是ThreadLocal,每个线程都有自己的ThreadLocalMap。如果map=null则直接执行setInitialValue().如果map已经创建,就表示Thread类的threadLocals属性已经初始化,如果e=null,依然会执行到setInitialVaue().
图中简要关系:
1个Thread有且仅有一个ThreadLocalMap对象
1个Entry对象的key弱引用指向1个ThreadLocal对象
1个ThreadLocalMap对象存储多个Entry对象
1个ThreadLocal对象可以被多个线程所共享
ThreadLocal对象不持有value,value由线程的Entry对象持有。
线程使用ThreadLocal的三个重要方法:
set---->如果set操作之后没有remove的ThreadLocal,容易引起脏数据
get----》始终没有get操作的ThreadLocal对象是没有意义的
remove----》如果没有remove,容易造成内存泄漏(因为static修饰的ThreadLocal,如果希望ThreadLocal失去对象引用,比如因为key是弱依赖,所以当线程变量的弱引用在gc的时候被回收,反而value无法回收,导致key为null,但是value不为null的情况,触发弱引用机制回收Entry的value就不现实了,所以需要remove掉。)和脏数据
ThreadLocal和InheritableThreadLocal透传上下文,需要注意线程之间的切换,异常传输的处理。避免因为处理不当导致的上下文丢失。
SimpleDateFormat是线程不安全的类,定义为static对象会有数据同步危险。推荐方式使用ThreadLocal,让每个线程单独拥有这个对象。
JUC-ThreadLocalRandom
JDK7新增的随机数生成器
1、随机数的类Random
比如你现在操作Random hello=new Random();
int 随机数=hello.nextInt();
//源码如下
public int nextInt() {
return this.next(32);
}
protected int next(int var1) {
AtomicLong var6 = this.seed;
long var2;
long var4;
do {
//获取当前原子变量的值
var2 = var6.get();
//根据当前的值计算新的值
var4 = var2 * 25214903917L + 11L & 281474976710655L;
//CAS自旋锁来保证多线程下的新的变量值在同一时刻只有一个线程可以操作
} while(!var6.compareAndSet(var2, var4));
//返回随机数
return (int)(var4 >>> 48 - var1);
}
2、ThreadLocalRandom
实现原理:通过ThreadLocal一样在每个线程维护一个变量值,每个线程根据自己的值进行计算,并使用新的更新原来的,再计算新的随机数,这样不需要自旋大量竞争。
JUC之原子操作类原理
原子性操作的类都是使用非阻塞算法CAS实现的,相比使用锁去实现原子性操作在性能上有很大的提高。原子性操作类的原理都大致相同。
- AtomicLong
- LongAdder
- LongAccumulator
AtomicLong
public class AtomicLong extends Number implements java.io.Serializable {
private static final long serialVersionUID = 1927816293512124184L;
// 初始化并通过 Unsafe.compareAndSwapLong 更新
由于其AtomicLong类是在rt.jar包下面,AtomicLong类是通过Boostrap类加载器进行加载的
private static final Unsafe unsafe = Unsafe.getUnsafe();
//存放变量value的偏移量
private static final long valueOffset;
//记录JVM底层是否支持Long类型无锁CAS
static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8();
//返回是否支持无锁CAS后将值保存到VM_SUPPORTS_LONG_CAS.
private static native boolean VMSupportsCS8();
static {
try {
//获取value在AtomicLong中的偏移量
valueOffset = unsafe.objectFieldOffset
(AtomicLong.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
//实际变量值
private volatile long value;
//创建给定的初始值
public AtomicLong(long initialValue) {
value = initialValue;
}
创建新的对象并带有初始值
public AtomicLong() {
}
返回当前值
public final long get() {
return value;
}
给当前value设置新值
public final void set(long newValue) {
value = newValue;
}
最后设置新值
public final void lazySet(long newValue) {
unsafe.putOrderedLong(this, valueOffset, newValue);
}
设置给定的值和返回新的值
public final long getAndSet(long newValue) {
return unsafe.getAndSetLong(this, valueOffset, newValue);
}
/**
* 原子性性设置值并返回结果
*
* @param expect期望值
* @param update更新值
* @return {@code true}false代表实际值和期望值不一样
*/
public final boolean compareAndSet(long expect, long update) {
return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
}
/**
原子性设置值,并返回期望值和结果值是否相等的结果
*
CAS的一种选择,并不能百分百保证顺序
*/
public final boolean weakCompareAndSet(long expect, long update) {
return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
}
/**
* 调用unsafe方法,原子性给当前值加1返回为递增后的值
* @return the previous value
*/
public final long getAndIncrement() {
return unsafe.getAndAddLong(this, valueOffset, 1L);
}
/**
* 调用unsafe方法,原子性给当前值减1返回为递减后的值
*
* @return the previous value
*/
public final long getAndDecrement() {
return unsafe.getAndAddLong(this, valueOffset, -1L);
}
/**
*原子性添加当前给定值并返回
*
* @param delta the value to add
* @return the previous value
*/
public final long getAndAdd(long delta) {
return unsafe.getAndAddLong(this, valueOffset, delta);
}
/**
**原子性给当前值加1并返回
*
* @return the updated value
*/
public final long incrementAndGet() {
return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
}
/**
**原子性给当前减少1并返回
*
* @return the updated value
*/
public final long decrementAndGet() {
return unsafe.getAndAddLong(this, valueOffset, -1L) - 1L;
}
/**
* 原子性添加给定值并返回结果值
*
* @param delta the value to add
* @return the updated value
*/
public final long addAndGet(long delta) {
return unsafe.getAndAddLong(this, valueOffset, delta) + delta;
}
/**
竞争失败返回之前值
* @since 1.8
*/
public final long getAndUpdate(LongUnaryOperator updateFunction) {
long prev, next;
do {
prev = get();
next = updateFunction.applyAsLong(prev);
} while (!compareAndSet(prev, next));
return prev;
}
/**
竞争失败返回更新值
* @since 1.8
*/
public final long updateAndGet(LongUnaryOperator updateFunction) {
long prev, next;
do {
prev = get();
next = updateFunction.applyAsLong(prev);
} while (!compareAndSet(prev, next));
return next;
}
/**
防止竞争失败返回之前值
* @since 1.8
*/
public final long getAndAccumulate(long x,
LongBinaryOperator accumulatorFunction) {
long prev, next;
do {
prev = get();
next = accumulatorFunction.applyAsLong(prev, x);
} while (!compareAndSet(prev, next));
return prev;
}
/**
防止竞争失败返回更新值
*
* @param x the update value
* @param 累加器函数是两个参数的无副作用函数
* @return the updated value
* @since 1.8
*/
public final long accumulateAndGet(long x,
LongBinaryOperator accumulatorFunction) {
long prev, next;
do {
prev = get();
next = accumulatorFunction.applyAsLong(prev, x);
} while (!compareAndSet(prev, next));
return next;
}
/**
将当前的值返回为string
*/
public String toString() {
return Long.toString(get());
}
/**
将当前的值返回int
*/
public int intValue() {
return (int)get();
}
/**
将当前值返回为long
*/
public long longValue() {
return get();
}
/**
将当前的long值强转为float类型
*/
public float floatValue() {
return (float)get();
}
/**
将当前的long值强转为double类型
*/
public double doubleValue() {
return (double)get();
}
}
高并发的场景下大量线程会同时去竞争更新同一个原子变量。但是由于同时只有一个线程的CAS操作会成功,这就导致了大量线程竞争失败后,会通过无限循坏不断进行自旋尝试CAS操作,而这会白白浪费CPU资源。
CAS___ABA问题------点击这里
优化的思路和实现------点击这里