何谓原子性操作,即为最小的操作单元,比如i=1,就是一个原子性操作,这个过程只涉及一个赋值操作。又如i++就不是一个原子操作,它相当于语句i=i+1;这里包括读取i,i+1,结果写入内存三个操作单元。因此如果操作不符合原子性操作,那么整个语句的执行就会出现混乱,导致出现错误的结果,从而导致线程安全问题。因此,在多线程中需要保证线程安全问题,就应该保证操作的原子性,那么如何保证操作的原子性呢?其一当然是加锁,这可以保证线程的原子性,比如使用synchronized代码块保证线程的同步,从而保证多线程的原子性。但是加锁的话,就会使开销比较大。另外,可以使用J.U.C下的atomic来实现原子操作。接下来我们就通过对比来说明atomic实现原子操作的功能。
1.非原子操作
首先,了解一下若不保证原子操作会出现什么样的情况。代码如下:
package concurrent;
import java.util.concurrent.*;
public class GeneralTest {
private static final ExecutorService es=Executors.newFixedThreadPool(10);
private static int count=0;
public static void main(String[] args) {
// TODO Auto-generated method stub
for(int i=0;i<20;i++)
{
Thread t1=new Thread(){
public void run(){
count+=1;
}
};
es.execute(t1);
}
for(int i=0;i<5;i++)
System.out.println("累加20次,得到结果是:"+count);
}
}
以上就是创建20个线程进行对count这个类变量自增一次,预期的值应该是20,但是运行上述程序出现如下结果:
累加20次,得到结果是:16
累加20次,得到结果是:19
累加20次,得到结果是:19
累加20次,得到结果是:19
累加20次,得到结果是:19
2.通过synchronized加锁
从以上结果可以看出,输出五次的结果都是没有达到20,可能出现的问题便是count+=1包含了三个操作,可能这个线程读取count的时候,上一个线程还没把更新的count值写入内存,这就是因无法保证操作的原子性而导致的线程安全问题。
synchronized称为互斥锁,它的工作原理就是一旦一个线程抢占了得到锁,其它线程便进入等待状态,只有当该线程释放锁,其它线程才有获取锁的机会。因此使用synchronized对count加锁,那么实现的操作便是原子性操作,因为只有当一个线程完成所有操作,然后释放锁,其它线程才会抢占锁。以下还是自增问题,当一个线程在实现++count时,只有当该线程将数据存入内存,释放锁之后,其它内存才会获取锁,读取数据,因此不会出现干涉问题,从而避免线程安全问题。
package concurrent;
import java.util.concurrent.*;
public class GeneralTest {
private static final ExecutorService es=Executors.newFixedThreadPool(20);
private static Integer count=0;
public static void main(String[] args) {
// TODO Auto-generated method stub
long start=System.currentTimeMillis();
for(int i=0;i<1000;i++)
{
Thread t1=new Thread(){
public void run(){
//count+=1;
synchronized(count)
{
System.out.println(Thread.currentThread().getName()+"实现了自增一次,结果为:"+(++count));
}
}
};
es.execute(t1);
}
try
{
Thread.sleep(5000);
}
catch(InterruptedException e)
{
e.printStackTrace();
}
System.out.println("计算过程的耗时为:"+(System.currentTimeMillis()-start-5000));
System.out.println("累加1000次,得到结果"+count);
}
}
运行以上程序,得到如下结果,由于累加1000次太多,所以不把每次加的结果显示出来,只显示一部分过程以及最终的结果。
pool-1-thread-8实现了自增一次,结果为:999
pool-1-thread-8实现了自增一次,结果为:1000
pool-1-thread-2实现了自增一次,结果为:679
pool-1-thread-14实现了自增一次,结果为:678
pool-1-thread-3实现了自增一次,结果为:677
pool-1-thread-1实现了自增一次,结果为:675
pool-1-thread-19实现了自增一次,结果为:674
pool-1-thread-13实现了自增一次,结果为:673
pool-1-thread-10实现了自增一次,结果为:882
pool-1-thread-7实现了自增一次,结果为:881
pool-1-thread-17实现了自增一次,结果为:715
pool-1-thread-9实现了自增一次,结果为:714
pool-1-thread-5实现了自增一次,结果为:713
pool-1-thread-11实现了自增一次,结果为:712
pool-1-thread-20实现了自增一次,结果为:706
pool-1-thread-18实现了自增一次,结果为:698
pool-1-thread-4实现了自增一次,结果为:697
pool-1-thread-16实现了自增一次,结果为:696
pool-1-thread-15实现了自增一次,结果为:695
pool-1-thread-6实现了自增一次,结果为:694
pool-1-thread-12实现了自增一次,结果为:693
计算过程的耗时为:13
累加1000次,得到结果1000
3.atomic实现
从上面可以看出,结果和预期一样,并且计算过程花费了13ms。
atomic系列的类在是J.U.C包下的一系列类。它主要包括四类:基本类型,数组类型,属性原子修改器类型,引用类型。
基本类型的类主要包括AtomicInteger、AtomicLong、AtomicBoolean等;数组类型主要包括AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray;属性原子修改器类型主要包括AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater ;引用类型主要包括AtomicReference、AtomicStampedRerence、AtomicMarkableReference。
1.基本类型的实现:
package concurrent;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicTest {
private static final AtomicInteger at=new AtomicInteger();
private static final ExecutorService es=Executors.newFixedThreadPool(20);
public static void main(String[] args) {
// TODO Auto-generated method stub
long start=System.currentTimeMillis();
for(int i=0;i<1000;i++)
{
Thread t1=new Thread() {
public void run()
{
System.out.println(Thread.currentThread().getName()+"实现了一次自增原子操作,结果为:"+at.incrementAndGet());
}
};
es.execute(t1);
}
try
{
Thread.sleep(5000);
}
catch(InterruptedException e)
{
e.printStackTrace();
}
System.out.println("计算过程的耗时为:"+(System.currentTimeMillis()-start-5000));
System.out.println("累加1000次,得到结果"+at);
}
}
运行结果如下:
pool-1-thread-13实现了一次自增原子操作,结果为:984
pool-1-thread-8实现了一次自增原子操作,结果为:983
pool-1-thread-14实现了一次自增原子操作,结果为:982
pool-1-thread-20实现了一次自增原子操作,结果为:981
pool-1-thread-10实现了一次自增原子操作,结果为:980
pool-1-thread-12实现了一次自增原子操作,结果为:979
pool-1-thread-3实现了一次自增原子操作,结果为:978
pool-1-thread-7实现了一次自增原子操作,结果为:977
pool-1-thread-4实现了一次自增原子操作,结果为:976
pool-1-thread-18实现了一次自增原子操作,结果为:1000
pool-1-thread-9实现了一次自增原子操作,结果为:999
pool-1-thread-17实现了一次自增原子操作,结果为:998
pool-1-thread-6实现了一次自增原子操作,结果为:997
pool-1-thread-5实现了一次自增原子操作,结果为:996
pool-1-thread-2实现了一次自增原子操作,结果为:995
计算过程的耗时为:9
累加1000次,得到结果1000
2.数组类型
由上面可知使用基本类型的原子操作类进行数字的自增,不仅可以保证操作操作的原子性,而且相对来说花费的时间代价比使用synchronized加锁的时间代价要小。
以下以AtomicIntegerArray为例通过对一个数组内的每个元素进行自增计算,从而来介绍数组类型的原子类的运用。
package concurrent;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicIntegerArray;
public class AtomicTest {
private static final AtomicIntegerArray at=new AtomicIntegerArray(new int[5]);
private static final ExecutorService es=Executors.newFixedThreadPool(20);
public static void main(String[] args) {
// TODO Auto-generated method stub
long start=System.currentTimeMillis();
for(int i=0;i<1000;i++)
{
Thread t1=new Thread() {
public void run()
{
for(int i=0;i<5;i++)
System.out.println(Thread.currentThread().getName()+"实现了对第"+(i+1)+"个元素一次自增原子操作,结果为:"+at.incrementAndGet(i));
}
};
es.execute(t1);
}
try
{
Thread.sleep(5000);
}
catch(InterruptedException e)
{
e.printStackTrace();
}
System.out.println("计算过程的耗时为:"+(System.currentTimeMillis()-start-5000));
for(int i=0;i<5;i++)
System.out.println("第"+(i+1)+"个元素累加1000次,得到结果"+at.get(i));
}
}
运行程序结果如下,其中运算过程部分给出:
pool-1-thread-3实现了对第5个元素一次自增原子操作,结果为:980
pool-1-thread-20实现了对第1个元素一次自增原子操作,结果为:989
pool-1-thread-20实现了对第2个元素一次自增原子操作,结果为:1000
pool-1-thread-20实现了对第3个元素一次自增原子操作,结果为:1000
pool-1-thread-20实现了对第4个元素一次自增原子操作,结果为:1000
pool-1-thread-20实现了对第5个元素一次自增原子操作,结果为:1000
pool-1-thread-15实现了对第5个元素一次自增原子操作,结果为:998
计算过程的耗时为:9
第1个元素累加1000次,得到结果1000
第2个元素累加1000次,得到结果1000
第3个元素累加1000次,得到结果1000
第4个元素累加1000次,得到结果1000
第5个元素累加1000次,得到结果1000
从结果可以看出,数组类型的原子类的使用,可以保证每个元素的自增等操作都满足原子性