目录
一、juc包存的意义【解决了synchronized没有解决的部分问题】
synchronized关键字不仅要解决原子性、可见性和有序性问题,还需要Object(ObjectMonitor)相关方法等周边配置一起才能解决所有的并发问题,虽然性能比较差但是jdk6之后做了大量的优化,性能问题已经解决了,那为什么还要juc(java.util.concurrent)包的存在呢?一方面是juc在jdk5就存在了,另一方面因为juc解决了synchronized中的下列问题:
一、juc包存的意义【解决了synchronized没有解决的部分问题】
1、能够响应中断
2、支持超时
3、非阻塞地获取锁
但是synchronized中锁解决的所有问题(或者功能),juc包中都必须解决,下面是自己整理的一个对比图:
二、juc包个人理解分为三个部分
1、atomic工具包
基于volatile + CAS机制,实现了AtomicInteger等执行并发的原则操作类,volatile基于内存屏障和HappenBefore原则解决了可见性和有序性问题,而CAS解决了原子性问题。CAS本身基于Unsafe的compareAndSwap以及compareAndSet相关api实现。
2、AQS
AQS(AbstractQueuedSynchronizer)本身对外的表现为虚拟的双向链表。AQS本身也包含了volatile + CAS机制解决原子性可见性和有序性问题;基于其内部类ObjectCondition(Condition的实现)基于LockSupport实现了类似Object的唤醒和等待的功能,当然LockSupport本身也是基于Unsafe包的等待唤醒api实现。AQS本身是一个抽象的模板方法类,有些具体的实现细节交给了子类,并且提供了公平锁和非公平锁两大类。
3、线程池
频繁的创建和销毁线程非常地消耗资源,那么需要线程池进行复用和管理。线程池也需要解决任务调度的问题,所以也使用到了AQS的功能,其中核心参数存储任务的队列BlockingQueue(比如常用的LinkedBlockingQueue)基于AQS实现;添加的任务ThreadPoolExecutor内部类Worker也是基于AQS实现。
三、Unsafe类
public final class Unsafe {
private static final sun.misc.Unsafe theUnsafe;
static {
registerNatives();
Reflection.registerMethodsToFilter(sun.misc.Unsafe.class, new String[]{"getUnsafe"});
theUnsafe = new sun.misc.Unsafe();
ARRAY_BOOLEAN_BASE_OFFSET = theUnsafe.arrayBaseOffset(boolean[].class);
// 省略部分类型
ARRAY_OBJECT_INDEX_SCALE = theUnsafe.arrayIndexScale(Object[].class);
ADDRESS_SIZE = theUnsafe.addressSize();
}
// 构造私有,在静态代码块中初始化完成(非懒加载)
private Unsafe() {
}
// 使用公共方法获取
@CallerSensitive
public static sun.misc.Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
// 管程的指令
public native void monitorEnter(Object var1);
public native void monitorExit(Object var1);
public native boolean tryMonitorEnter(Object var1);
// 获取设置
public native int getInt(Object var1, long var2);
public native void putInt(Object var1, long var2, int var4);
// Cas指令
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
// LockSupport依赖的指令
public native void unpark(Object var1);
public native void park(boolean var1, long var2);
// x86底层使用的命令是 加载屏障、存储屏障和全屏障(类似StoreLoadBarrier的全能屏障)
public native void loadFence();
public native void storeFence();
public native void fullFence();
}
Unsafe类可以说是Juc存在的关键,提供了很多native的方法,直接调用底层指令,不如CAS指令等。在Jdk9之前,Usafe类只允许处理BootstrapClassLoader加载的类,根据双亲委派模型可知我们自己写的类则是使用AppClassLoader或者其子类加载完成,也就是说Jdk9之前我们不能直接使用Unsafe类写自己的工具等(当然juc也比较全了,并且是大师Doug Lea操刀)。
1、CAS(如上compareAndSwapInt)【乐观锁的思想】
// Cas指令
public final native boolean compareAndSwapInt(Object address, long offset, int expected, int value);
第1个参数一般调用时传入this,则可以找到内存地址;读取传入对象address在内存中偏移量的值与期望的值,如果相等就把x值赋值给offset,并且返回true,否则就取消操作,并返回false。而该native方法,直接调用的底层对应的CAS指令。
2、提供了线程等待和唤醒的方法(park和unpark)
而在juc包下基本都是使用工具类LockSupport间接进行调研,在并发编程基础 - Thread状态和生命周期中我们也得知,该工具的操作会伴随着线程状态的转换。
park(Long):传入一个等待的时间,若传入0L则一直等待没有超时。此时线程由 RUNNABLE 切换为 WAITING或TIMED_WAITING状态。
unpark():传入当前线程等参数,此时线程由WAITING切换为RUNNABLE状态,TIMED_WAITING时间到了会自动切换。
3、提供了x86底层使用的加载屏障、存储屏障和全屏障【全屏障 类似StoreLoadBarrier】
4、提供了管程相关方法:monitorEnter、monitorExit、tryMonitorEnter
5、该类构造私有化,并且在静态代码块中直接初始化(非懒加载),当所有地方调用时返回同一对象(单利模式)。
四、CAS存在的问题和解决方案
1、ABA问题
CAS会伴随着while(true)这样的循环重试机制,比如叠加器,根据当前的值0进行+1,判断期望内存值为0则把内存值赋值为1(即:0+1),如果与期望的值不符合则说明内存值被其他线程修改过,则需要获取最新的值+1再重试;当然一般该值会是volatile修饰的读取操作会获取到最新的内存值。
但是如果内存中的值与期望的值一样,就能说明没有被其他线程修改过吗?当然不是,比如线程B 将值 +1,线程C将值 -1,那么内存值与期望值是相等的,但是曾经被操作过,这就是ABA的问题。像叠加这样的场景允许ABA问题存在,对后续结果没有影响;但是并不是所有的业务场景都允许ABA问题存在,解决的办法也比较简单就是乐观锁的思想,增加版本号。
2、循环时间长开销比较大
自旋CAS本身会消耗一定的CPU资源,长时间不成功消耗CPU资源。
3、只能保证一个共享变量的原子操作
CAS的第一个参数就是为了获取对象的内存地址的,他不能像管程那样执行的代码片段只要中间状态对外不可见即可。但是我们使用CAS进行操作时,怎么同时处理多个对象的原子性呢,AtomicRefrence类保证了引用对象之间的原子性。