在java语言中,类似i++这种操作并不是原子性的。它并非线程安全的语句,在实际使用中,我们经常需要使用synchronized语句来保证数据的正确。现如今,大多数处理器都包含原子性指令,常见的指令是CAS(compare and set)和TAS(test and set),是一种加锁的原子操作指令。
CAS 操作
包含三个操作数 ——内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。CAS执行结果要么成功要么失败,对于失败的情形下一班采用不断重试。或者放弃。
在java.util.concurrent.atomic包中,所有类都使用了原子变量,它并不用同步来实现,因此大大提高了效率。
atomic包含有的类如下:
AtomicBoolean 可以用原子方式更新的 boolean 值。
AtomicInteger 可以用原子方式更新的 int 值。
AtomicIntegerArray 可以用原子方式更新其元素的 int 数组。
AtomicIntegerFieldUpdater<T> 基于反射的实用工具,可以对指定类的指定 volatile int 字段进行原子更新。
AtomicLong 可以用原子方式更新的 long 值。
AtomicLongArray 可以用原子方式更新其元素的 long 数组。
AtomicLongFieldUpdater<T> 基于反射的实用工具,可以对指定类的指定 volatile long 字段进行原子更新。
AtomicMarkableReference<V> AtomicMarkableReference 维护带有标记位的对象引用,可以原子方式对其进行更新。
AtomicReference<V> 可以用原子方式更新的对象引用。
AtomicReferenceArray<E> 可以用原子方式更新其元素的对象引用数组。
AtomicReferenceFieldUpdater<T,V> 基于反射的实用工具,可以对指定类的指定 volatile 字段进行原子更新。
AtomicStampedReference<V> AtomicStampedReference 维护带有整数“标志”的对象引用,可以用原子方式对其进行更新。
public class AtomicBooleanTest {
/**
06. * 主要方法:
07. * @see AtomicBoolean#compareAndSet(boolean, boolean) 第一个参数为原始值,第二个参数为要修改的新值,若修改成功则返回true,否则返回false
08. * @see AtomicBoolean#getAndSet(boolean) 尝试设置新的boolean值,直到成功为止,返回设置前的数据
09. */
public final static AtomicBoolean TEST_BOOLEAN = new AtomicBoolean();
public static void main(String[] args){
for(int i=0;i<10;i++){
new Thread(){
public void run(){
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
if(TEST_BOOLEAN.compareAndSet(false, true)){
System.out.println("我是线程"+Thread.currentThread().getName()+"我成功了!");
}else
System.out.println("我是线程"+Thread.currentThread().getName()+"我失败了!");
}
}.start();
}
}
}
public class AtomicIntegerTest {
public final static AtomicInteger TEST_INTEGER=new AtomicInteger(1);
public static void main(String[] args)throws InterruptedException {
final Thread[] threads=new Thread[10];
for(int i=0;i<10;i++){
final int num=i;
threads[i]=new Thread(){
public void run(){
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
int now=TEST_INTEGER.incrementAndGet();
System.out.println("我是线程"+num+",我得到了值,增加后的值为:"+now);
}
};
threads[i].start();
}
for(int i=0;i<10;i++){
threads[i].join();
}
System.out.println("最终结果:"+TEST_INTEGER.get());
}
}
public class AtomicIntegerArrayTest {
private final static AtomicIntegerArray ATOMIC_INTEGER_ARRAY = new AtomicIntegerArray(new int[]{1,2,3,4,5,6,7,8,9,10});
public static void main(String []args) throws InterruptedException {
Thread []threads = new Thread[100];
for(int i = 0 ; i < 100 ; i++) {
final int index = i % 10;
final int threadNum = i;
threads[i] = new Thread() {
public void run() {
int result = ATOMIC_INTEGER_ARRAY.addAndGet(index, index + 1);
System.out.println("线程编号为:" + threadNum + " , 对应的原始值为:" + (index + 1) + ",增加后的结果为:" + result);
}
};
threads[i].start();
}
for(Thread thread : threads) {
thread.join();
}
System.out.println("=========================>\n执行已经完成,结果列表:");
for(int i = 0 ; i < ATOMIC_INTEGER_ARRAY.length() ; i++) {
System.out.println(ATOMIC_INTEGER_ARRAY.get(i));
}
}
}
ABA问题:如果一个线程修改V值假设原来是A,先修改成B,再修改回成A。当前线程的CAS操作无法分辨当前V值是否发生过变化。举个例子,你把屋子收拾干净之后,有事出门一趟,这时屋子整齐的状态成为A,在你出门的这段时间,有人把你的屋子弄乱,翻来翻去。这时脏乱的状态成为B,之后,又帮你恢复到原样,当你回家的时候,你看到的是状态A,你以为你的屋子一直存在于状态A,而实际上它经历了ABA这个过程。
public class AtomicReferenceTest {
public final static AtomicReference <String>ATOMIC_REFERENCE = new AtomicReference<String>("abc");
public static void main(String[] args){
for (int i=0;i<100;i++){
final int num=i;
new Thread(){
@Override
public void run(){
try{
Thread.sleep((int)(Math.random()*100));
}catch(InterruptedException e){
e.printStackTrace();
}
if(ATOMIC_REFERENCE.compareAndSet("abc", "abc2")){
System.out.println("我是线程"+num+"我获得了锁进行了对象修改!");
}
}
}.start();
}
new Thread(){
@Override
public void run(){
while(!ATOMIC_REFERENCE.compareAndSet("abc2", "abc"));
System.out.println("已经改为原始值!");
}
}.start();
}
}
public class AtomicStampedReferenceTest {
/*
* 将abc修改为abc2的线程仅有一个被访问,虽然被修改回了原始值,但
* 是其他线程也不会再将abc改为abc2。
*/
public final static AtomicStampedReference <String>ATOMIC_REFERENCE = new AtomicStampedReference<String>("abc" , 0);
public static void main(String []args) {
for(int i = 0 ; i < 100 ; i++) {
final int num = i;
final int stamp = ATOMIC_REFERENCE.getStamp();
new Thread() {
public void run(){
try {
Thread.sleep(Math.abs((int)(Math.random() * 100)));
}catch (InterruptedException e){
e.printStackTrace();
}
if(ATOMIC_REFERENCE.compareAndSet("abc" , "abc2" , stamp , stamp + 1)){
System.out.println("我是线程:" + num + ",我获得了锁进行了对象修改!");
}
}
}.start();
}
new Thread(){
public void run(){
int stamp=ATOMIC_REFERENCE.getStamp();
while(!ATOMIC_REFERENCE.compareAndSet("abc2", "abc" , stamp , stamp + 1));
System.out.println("已经改回为原始值!");
}
}.start();
}
}
总结:使用atomic包下的各种类来处理线程线程之间的竞争要比使用synchronized关键字要高效的多,尽量使用原子类。