(1)对于count++这种操作来说,通常使用synchronized关键字保证原子更新操作,synchronized会保证多线程不会同时更新变量count。但是,使用synchronzied成本太高了,需要先获取锁,最后还要释放锁,获取不到锁的情况下还要等待,还会有线程的上下文切换,这些都需要成本。
(2)而Java从Jdk 1.5开始提供了java.util.concurrent.atomic包(并发包),这个包中的原子操作类提供了一种用法简单、性能高效、线程安全的更新一个变量的方式。并发包基本原子变量类型有:原子更新基本类型、原子更新数组、原子更新引用、和原子更新属性(字段)。
(3)之所以称为原子变量,是因为其包含一些以原子方式实现组合操作的方法。
(4)JAVA1.5开始引入了CAS,主要代码都放在JUC的atomic包下,如下图:
// 第一个构造方法给定了一个初始值,第二个的初始值为0。
public AtomicInteger(int initialValue)
public AtomicInteger()
// 可以直接获取或设置AtomicInteger中的值,方法是:
public final int get()
public final void set(int newValue)
// 以原子方式获取旧值并设置新值
public final int getAndSet(int newValue)
// 以原子方式获取旧值并给当前值加1
public final int getAndIncrement()
// 以原子方式获取旧值并给当前值减1
public final int getAndDecrement()
// 以原子方式获取旧值并给当前值加delta
public final int getAndAdd(int delta)
// 以原子方式给当前值加1并获取新值
public final int incrementAndGet()
// 以原子方式给当前值减1并获取新值
public final int decrementAndGet()
// 以原子方式给当前值加delta并获取新值
public final int addAndGet(int delta)
(1)测试源码
public class AtomicIntegerTest {
public static void mainTest() {
// testAtomicInteger(true); // 测试int
testAtomicInteger(false); // 测试AtomicInteger
}
// 数量达到一定才容易看效果
private static final int THREADS_COUNT = 10000;
private static final int NUM = 100;
private static int sCount = 0;
private static AtomicInteger sAtomicCount = new AtomicInteger(0);
private static void testAtomicInteger(final boolean isAtomicInteger) {
Thread[] threads = new Thread[THREADS_COUNT];
for (int i = 0; i < THREADS_COUNT; i++) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < NUM; i++) {
try {
Thread.sleep(10);
if (isAtomicInteger) {
sAtomicCount.incrementAndGet();
} else {
sCount++;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
threads[i].start();
}
// 让调用join()的线程先执行
for (int i = 0; i < THREADS_COUNT; i++) {
try {
threads[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 再执行主线程
System.out.println(isAtomicInteger ? "AtomicInteger:" + sAtomicCount.get() : "Int:" + sCount);
}
}
(2)对于Java中的运算操作,例如自增或自减,若没有进行额外的同步操作,在多线程环境下就是线程不安全的。count++解析为count=count+1,明显,这个操作不具备原子性,多线程并发共享这个变量时必然会出现问题。结果如下:
2019-08-25 21:18:20.159 16917-16917/com.seniorlibs.thread I/System.out: Int:999323
2019-08-25 21:19:14.190 27071-27071/com.seniorlibs.thread I/System.out: Int:997635
(3)用了AtomicInteger类后会变成什么样子呢?
2019-08-25 21:17:46.887 6623-6623/com.seniorlibs.thread I/System.out: AtomicInteger:1000000
2019-08-25 21:17:46.887 6623-6623/com.seniorlibs.thread I/System.out: AtomicInteger:1000000
结果每次都输出1000000,程序输出了正确的结果,这都归功于AtomicInteger.incrementAndGet()方法的原子性。
(1)同步:多线程并发访问共享数据时,保证共享数据再同一时刻只被一个或一些线程使用。
(2)阻塞同步和非阻塞同步都是实现线程安全的两个保障手段,非阻塞同步对于阻塞同步而言主要解决了阻塞同步中线程阻塞和唤醒带来的性能问题。
(3)那什么叫做非阻塞同步呢?在并发环境下,某个线程对共享变量先进行操作,如果没有其他线程争用共享数据那操作就成功;如果存在数据的争用冲突,那就才去补偿措施,比如不断的重试机制,直到成功为止,因为这种乐观的并发策略不需要把线程挂起,也就把这种同步操作称为非阻塞同步(操作和冲突检测具备原子性)。
(1)在硬件指令集的发展驱动下,使得 “操作和冲突检测” 这种看起来需要多次操作的行为只需要一条处理器指令便可以完成,这些指令中就包括非常著名的CAS指令(Compare-And-Swap比较并交换)。
(2)将内存位置的内容与给定期望值进行比较,只有在相同的情况下,将该内存位置的内容修改为更新值,这是作为单个原子操作完成的。 原子性保证新值基于最新信息计算,如果该值在同一时间被另一个线程更新,则写入将失败。 操作结果必须说明是否进行替换,这可以通过一个简单的布尔响应,或通过返回从内存位置读取的值来完成。
(1)CAS可以有效的提升并发的效率,但同时也会引入ABA问题。
(2)如线程1从内存X中取出A,这时候另一个线程2也从内存X中取出A,并且线程2进行了一些操作将内存X中的值变成了B,然后线程2又将内存X中的数据变成A,这时候线程1进行CAS操作发现内存X中仍然是A,然后线程1操作成功。虽然线程1的CAS操作成功,但是整个过程就是有问题的。比如链表的头在变化了两次后恢复了原值,但是不代表链表就没有变化。
(3)所以Java中提供了AtomicStampedReference/AtomicMarkableReference来处理会发生ABA问题的场景,主要是在对象中额外再增加一个标记来标识对象是否有过变更。
/**
* Atomically increments by one the current value.
* @return the updated value
*/
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
incrementAndGet()方法在一个无限循环体内,不断尝试将一个比当前值大1的新值赋给自己,如果失败则说明在执行"获取-设置"操作的时已经被其它线程修改过了,于是便再次进入循环下一次操作,直到成功为止。这个便是AtomicInteger原子性的"诀窍"了,继续进源码看它的compareAndSet方法:
/**
* 如果内存位置的内容{@code ==}是给定期望值,则原子性地将该值设置为给定的更新值。
*
* @param 给定期望值
* @param 更新值
* @成功返回true,false表示内存位置的内容不等于给定期望值。
*/
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
JAVA中的CAS操作都是通过sun包下Unsafe类实现,而Unsafe类中的方法都是native方法,由JVM本地实现,,查看了openJDK7的源码,下面就稍作分析:
最后调用的是Atomic:comxchg这个方法,这个方法的实现放在hotspot下的os_cpu包中,说明这个方法的实现和操作系统、CPU都有关系,以linux的X86处理器的实现为例来进行分析:
inux的X86下主要是通过cmpxchgl这个指令在CPU级完成CAS操作的,但在多处理器情况下,CAS操作操作必须使用lock指令加锁来完成原子性操作,从这个例子就可以比较清晰的了解CAS的底层实现了。
原子操作类AtomicInteger详解
Java编程的逻辑(70) - 原子变量和CAS
Java中的CAS实现原理