共享对象
使用Java编写线程安全的程序关键在于正确的使用共享对象,以及安全的对其进行访问管理。Java的内置锁可以保障线程安全,对于其他的应用来说并发的安全性是使用内置锁保障了线程变量使用的边界。谈到线程的边界问题,随之而来的是Java内存模型另外的一个重要的含义,可见性。Java对可见性提供的原生支持是volatile关键字。
Atomic
作用
对于原子操作类,Java的concurrent并发包中主要为我们提供了这么几个常用的:AtomicInteger、AtomicLong、AtomicBoolean、AtomicReference。
对于原子操作类,最大的特点是在多线程并发操作同一个资源的情况下,使用Lock-Free算法来替代锁,这样开销小、速度快,对于原子操作类是采用原子操作指令实现的,从而可以保证操作的原子性。
通常情况下,在Java里面,++i或者–i不是线程安全的,这里面有三个独立的操作:获得变量当前值,为该值+1/-1,然后写回新的值。在没有额外资源可以利用的情况下,只能使用加锁才能保证读-改-写这三个操作是“原子性”的。
Java 5新增了AtomicInteger类,该类包含方法getAndIncrement()以及getAndDecrement(),这两个方法实现了原子加以及原子减操作,但是比较不同的是这两个操作没有使用任何加锁机制,属于无锁操作。
它会在这步操作都完成情况下才允许其它线程再对它进行操作,而这个实现则是通过Lock-Free+原子操作指令来确定的
AtomicInteger类中:
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
public final int get() {
return value;
}
private volatile int value;
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
可以看到是一个cas原子操作。
unsafe是java用来在CPU级别的操作CAS指令的类,对于程序员来说,此类是不可用。
由于是cpu级别的指令,其开销比需要操作系统参与的锁的开销小。
对于多个线程进入时,会先比较现在的value 是否与expect相等,如果不相等,则进入下一个循环。如果相等,则会更新成update值。
之后再进入的线程则会死循环。这样就保证了操作的原子性。
这样一个方法中 即包含了原子性,又包含了可见性
而关于Lock-Free算法,则是一种新的策略替代锁来保证资源在并发时的完整性的,Lock-Free的实现有三步:
- 循环(for(;😉、while)
- CAS(CompareAndSet)
- 回退(return、break)
用法
比如在多个线程操作一个count变量的情况下,则可以把count定义为AtomicInteger,如下:
public class Counter {
private AtomicInteger count = new AtomicInteger();
public int getCount() {
return count.get();
}
public void increment() {
count.incrementAndGet();
}
在每个线程中通过increment()来对count进行计数增加的操作,或者其它一些操作。这样每个线程访问到的将是安全、完整的count。
内部实现
采用Lock-Free算法替代锁+原子操作指令实现并发情况下资源的安全、完整、一致性
ABA问题(AtomicStampedReference的使用)
public class ABA {
// 普通的原子类,存在ABA问题
AtomicInteger a1 = new AtomicInteger(10);
// 带有时间戳的原子类,不存在ABA问题,第二个参数就是默认时间戳,这里指定为0
AtomicStampedReference<Integer> a2 = new AtomicStampe