当程序更新一个变量时,如果多线程同时更新这个变量,就可能出现数据不同步的情况。
这种多线程的更新操作,一般我们都是使用的synchronized来解决这个问题,但是这种基于对象监视器(monitorenter 和 monitorexit 指令)的方式是一种很吃性能的解决方案,虽然后面Java提供了各种锁,比如偏向锁,轻量级锁,但是基于锁的膨胀机制,高并发场景,最后还是会这些锁还是会升级为重量级锁synchronized,而且这种升级是不能在变化的,也就是说可能某一刻高并发严重,我们迫不得已的确需要使用重量级锁,但是在并发小的情况下,锁无法自动降级。也就是说这种锁,还不智能,期待后期Java版本可以提供更加智能的API,现在我们更多使用的是volite修饰配合CAS实现的高并发下的同步需求。
Volite底层依赖总线锁和缓存锁,可以保证多线程下的对单个Volite变量的数据一致性。
总线锁 依赖处理机使用内存时在总线上发送的LOCK#信号,可以锁定总线,独占整个总线。
缓存锁 是依靠缓存一致性机制来保证,缓存一致性机制会阻止同时修改两个以上处理器缓存的内存区的数据,当其他处理器回写了被锁定的缓存行数据时,会使缓存行失效。
从JDK1.5开始提供的java.util.concurrent.atomic包,就是基于第二种方案(使用Volite配合UnSafe的CAS实现的),这个包下面提供了一系列的用法简单,性能高效,线程安全的更新一个变量方式。
Atomic下主要有4种类型的更新方式:
- 原子更新基本类型
- 原子更新数组类型
- 原子更新引用
- 原子更新属性(字段)
原子更新基本类型
主要包括了AtomicBoolean,AtomicInteger,AtomicLong分别对应boolean,int 和 long类型的变量。
由于这3个类提供的方法几乎一模一样,这里就使用用的比较多AtomicInteger为例,介绍一下它们的用法以及底层实现
- 用法:
import java.util.concurrent.atomic.AtomicInteger;
public class Test {
static AtomicInteger ai = new AtomicInteger(1);
public static void main(String[] args) {
System.out.println("int addAndGet(int delta) 先相加,然后返回 初值加delta的结果===="
+ ai.addAndGet(1));// ++i;
System.out
.println("boolean compareAndSet(oldValue,newValue) 就是先判断初值是不是oldValue,是的话就尝试修改为newVlaue,成功就返回true,否则返回false");
boolean flag = ai.compareAndSet(3, 5);
System.out.println(flag);
boolean flag2 = ai.compareAndSet(2, 3);
System.out.println(flag2);
System.out.println("int getAndIncrement()先返回初值,在自增+1 ===="
+ ai.getAndIncrement());// i++;
System.out
.println("void lazySet(int newValue)把Volite的语义去掉的增加,比较低级的加,允许在一段时间内不能保证一致性 ====");
ai.lazySet(4);
System.out.println("最终结果:" + ai.get());
}
}
结果:
int addAndGet(int delta) 先相加,然后返回 初值加delta的结果====2
boolean compareAndSet(oldValue,newValue) 就是先判断初值是不是oldValue,是的话就尝试修改为newVlaue,成功就返回true,否则返回false
false
true
int getAndIncrement()先返回初值,在自增+1 ====3
void lazySet(int newValue)把Volite的语义去掉的增加,比较低级的加,允许在一段时间内不能保证一致性 ====
最终结果:4
- 源码分析:
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
在一个循环里面不停地尝试进行CAS,一旦成功就直接返回,通过cpu自旋来实现,compareAndSet是由unSafe提供,底层直接操作硬件,这个操作的原子性是由硬件来保证的,脱离了Java的规范,所以Java叫它unSafe。但是这个方案还是有点问题,就是很容易到时cpu长时间空旋,所以java8之后,这个getAndIncrement()方法直接封装到unSafe上,内部据说也做长时间cpu空旋问题的优化。
原子更新数组类型
主要包括了AtomicLongArray,AtomicIntegerArray,AtomicReferenceArray,常见方法如下:
int addAndGet(int i,int delta);原子方式将数组索引 i
的元素+delta
boolean compareAndSet(int i,int except,int update);原子方式对数组i
处的元素进行CAS尝试。
- 用例:
import java.util.concurrent.atomic.AtomicIntegerArray;
public class Test {
static int[] a = new int[] { 1, 2 };
static AtomicIntegerArray aIntegerArray = new AtomicIntegerArray(a);
public static void main(String[] args) {
aIntegerArray.getAndSet(0, 4);
int value = aIntegerArray.get(0);
System.out.println(value);
System.out.println(a[0]);
}
}
结果:
4
1
注意上面的代码在JDK1.8运行。
原子更新引用
上面提供的都是只能保证更新一个变量的一致性,但是大多数情况下,我们要保证的同时更新多个变量的一致性,我们可以把这些变量临时放在一个原子的引用里面。Atomic包提供了如下的方式进行引用类型变量的更新。
- 用法:
AtomicReference:原子更新引用类型
AtomicReferenceUpdater:原子更新引用类型里面的字段
AtomicMarkReference:原子更新带有标记位的引用类型,原子更新一个boolean类型标记位和引用类型,主要为了防止ABA问题。
用例:
import java.util.concurrent.atomic.AtomicReference;
public class Test {
static AtomicReference ai = new AtomicReference();
public static void main(String[] args) {
User user1 = new User();
user1.setName("ggr");
user1.setPassword("3621");
ai.set(user1);
User user2 = new User();
user2.setName("ccc");
user2.setPassword("3621");
ai.compareAndSet(user1, user2);
System.out.println(ai.get().toString());
}
static class User{
private String name;
private String password;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User [name=" + name + ", password=" + password + "]";
}
}
}
结果:
User [name=ccc, password=3621]
原子更新属性(字段)
如果需要原子更新某个类的某个字段时,就需要使用原子更新字段类,Atomic也提供了3个类进行原子字段更新:
AtomicIntegerFileUpdater:原子更新整型字段的更新器
AtomicIntegerLongUpdater:原子更新整Long段的更新器
AtomicIntegerStampedUpdater:原子更新带版本号的引用类型
- 用法:这些都是抽象类,每次使用要使用它的静态方法newUpdater()创建一个更新器,而且更新的字段必须被Volite修饰。
用例:
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
public class Test {
static AtomicIntegerFieldUpdater ai = AtomicIntegerFieldUpdater
.newUpdater(User.class, "age");
public static void main(String[] args) {
User user1 = new User();
user1.setName("ggr");
user1.setPassword("3621");
user1.age = 24;
ai.getAndIncrement(user1);
System.out.println(user1.toString());
}
static class User {
volatile String name;
private String password;
volatile int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User [name=" + name + ", age=" + age + "]";
}
}
}
结果:
User [name=ggr, age=25]
当然Atomic里面还有其他的原子类型操作API,更多可以自行研究。