线程安全-非阻塞同步
互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能问题。随着硬件指令集的发展,有了另一个选择:基于冲突检测的乐观锁并发策略。先进行操作,如果没有其他线程争用共享数据,则操作成功;如果共享数据有争用,产生了冲突,那就采取其他的补充措施(最常见的就是不断地重试,直到成功为止)。这种乐观锁的并发策略不需要把线程挂起,因此这种同步操作称之为非阻塞同步。
乐观锁实现前提
需要操作和冲突检测这两个步骤保证原子性,也就是只通过一条指令就能完成。
常见指令包括:
测试并设置(Test and Set)
获取并增加(Fetch and Increment)
交换(Swap)
比较与交换(Compare and Swap)
加载链接/条件存储(Load Linked/Store Conditional)
乐观锁实现-CAS
CAS对应底层cmpxchg指令,需要3个操作数,内存位置V,旧的预期值A,新值B。CAS指令执行时,当且仅当V符号预期值A时,处理器用新值B更新V的值,否则它就不执行操作。该过程是一个原子操作。
java程序中的CAS操作由sun.misc.Unsafe类里边的compareAndSwapInt()、sompareAndSwapLong()和compareAndSwapObject()等几个方法包装提供。
java-CAS方法参数:修改对象(内存位置),偏移量(该属性在对象(头文件)的哪个位置),原值,修改值。
cas一般与volatile配合使用,保证原子性,可见性。
ABA问题
如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然为A值,那么就可以认定它的值没有被其他线程修改过吗?如果这段时间变量V的值中间被改为了B,后来又改回了A,那么CAS操作会误认为它从来没有被改变过。这个漏洞被称为"ABA"问题。
ABA发生的重点原因就是过程无法跟踪。
举例: 线程1拿到了a的值=1,线程2修改了a的值=2,然后做了一系列操作后,又恢复了a的值=1,这时线程1进行CAS操作发现没问题。
ABA问题解决
1.加版本概念,CAS除了比较值还要比较版本。
JUC提供了了AtomicStampedRefence类,通过控制变量值的版本来保证CAS的正确性。
2.改用传统的互斥同步。
Atomic
原子:不能被进一步分割的最小粒子
原子性:提供了互斥访问,同一时刻只能有一个线程对它进行操作
原子操作:不可被中断的一个或一系列操作
在atomic包里一共有12个类,Atomic包里的类基本都是使用Unsafe实现的包装类。
以AtomicInteger为例分析
内部结构
// 反射获取魔术类Unsafe
private static final Unsafe unsafe = Unsafe.getUnsafe();
// 偏移量 作为unsafe.CAS参数
private static final long valueOffset;
static {
try {
// 偏移量取值为value属性,在该对象文件头的内存位置
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
// 具体值
private volatile int value;
// CAS 当前值为预期值expect时,更新当前值为新值update
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
Unsafe使用
Unsafe不是提供给用户程序调用的类,Unsafe.getUnsafe()代码中限制了只有启动类加载器(Bootstrap ClassLoader)加载的Class才能访问它。
Unsafe引用方式:
1.提供反射创建调用
2.通过java命令行把调用Unsafe的类所在包路径添加到默认boostrap包路径,使得该类被引导类加载器加载
sun.misc.Unsafe使用参考
网友评论