线程(七)原子操作

何谓原子性操作,即为最小的操作单元,比如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

从结果可以看出,数组类型的原子类的使用,可以保证每个元素的自增等操作都满足原子性

猜你喜欢

转载自blog.csdn.net/u014252478/article/details/83539575