Java的原子类

简介

java.util.concurrent.atomic包下有Java提供的线程安全的原子类。

原子类内部使用volatile确保可见性和有序性,使用Unsafe提供的CAS方法确保原子性。

实例分析

下面通过计数器的例子介绍原子类AtomicInteger的使用并简单的分析AtomicInteger的部分源码

非线程安全的计数器

package com.rjh.threadsafe;	

/**
 * 非线程安全的计数器类
 * @author RJH
 * 2017年11月30日
 */
public class Counter {

	/**
 	* 从0开始计数
 	*/
	private int i=0;
	/**
 	 * 不断自增
 	 * @author RJH
 	 * @return
 	 */
	public int increase(){
		return ++i;
	}
	/**
 	 * 输出当前计数器的值
 	 * @author RJH
 	 * @return
 	 */
	public int currentCount(){
		return i;
	}
}

测试用例代码

运行测试用例代码的时候可以多次运行并比较输出结果,如果输出结果较为接近,这样的输出结果就会相对准确。

	package com.rjh.threadsafe;
	/**
 	 * 计数器测试类
 	 * @author RJH
 	 * 2017年11月30日
 	 */
	public class CounterTest {
		public static void main(String[] args) {
			//构建一个计数器实例
			Counter counter=new Counter();
			//线程个数
			int threadNum=1000;
			//每个线程的点击次数
			int times=10000;
			long start=System.currentTimeMillis();
			for(int i=0;i<threadNum;i++){
				ClickThread t=new ClickThread(counter,times);
				t.start();
			}
			//当前线程组活动线程大于1则不断等待(确保所有点击线程已经终止)
			while(Thread.activeCount()>1){
			
			}
			System.out.println("TotalTime:"+(System.currentTimeMillis()-start));
			System.out.println("Count:"+counter.currentCount());
		}
		/**
	 	 * 点击线程,模拟点击事件触发计数器
	 	 * @author RJH
	 	 * 2017年11月30日
		 */
		private static class ClickThread extends Thread{
			/**
			 * 计数器
			 */
			private Counter counter;
			/**
			 * 点击总次数
		 	*/
			private int times;
		
			public ClickThread(Counter counter,int times) {
				this.counter = counter;
				this.times=times;
			}
		
			@Override
			public void run(){
				for(int i=0;i<times;i++){//触发计数器指定次数
					counter.increase();
				}
			}
		}
	}

非线程安全的计数器测试结果与分析

期待结果

Count应该为threadNum*times,即1000X10000=10000000

运行结果
TotalTime:56
Count:9947180
分析

在例子中的++i不是原子性的,这段代码执行了3步操作:

  1. 读取i的旧值
  2. 修改i的值
  3. 重新写入i的新值

而在实际运行的时候,由于可能存在多个线程读取相同的i的旧值并在修改后写入,所以出现与预期结果不一致的情况。

线程安全的计数器

接下来在非线程安全的计数器类上改为使用原子类AtomicInteger把该计数器类改为线程安全的。

	package com.rjh.threadsafe;

	import java.util.concurrent.atomic.AtomicInteger;

	/**
	 * 线程安全的计数器类
	 * @author RJH
 	 * 2017年11月30日
 	 */
	public class Counter {
		/**
		 * 从0开始计数
		 */
		private AtomicInteger i=new AtomicInteger(0);
		/**
	 	 * 不断自增
	 	 * @author RJH
	 	 * @return
	 	 */
		public int increase(){
			return i.incrementAndGet();
		}
		/**
	 	 * 输出当前计数器的值
	 	 * @author RJH
	 	 * @return
	 	 */
		public int currentCount(){
			return i.get();
		}
	}

线程安全的计数器测试结果与分析

期待结果

Count应该为threadNum*times,即1000X10000=10000000

运行结果
	TotalTime:625
	Count:10000000
分析

使用原子类AtomicInteger虽然确保了线程安全,但是明显看出花费了更多的时间。后面我会对AtomicIntegeraddAndGet()方法进行源码分析。

AtomicInteger源码分析

	/**
     * 以原子操作的方式将当前值加 1。
     * @return 更新后的值
     */
	public final int incrementAndGet() {
        for (;;) {//等价于while(true)
            int current = get();//获取当前的值
            int next = current + 1;//自增后为更新后的值
            if (compareAndSet(current, next))//如果更新成功则返回更新后的值(循环结束),否则进入下一次循环
                return next;
        }
    }
	
	/**
	 * 如果当前值等于期待的值则把将期待的值以原子操作的方式改为更新后的值
	 * 
     * @param 当前期待的值
     * @param 更新后的值
     * @return 更新成功返回true,如果当前实际的值不等于期待的值则返回false
     */
    public final boolean compareAndSet(int expect, int update) {
		//Unsafe类提供CAS,valueOffset是使用Unsafe.objectFieldOffset()方法获取到的字段偏移量
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

每次CAS失败都会去获取当前的值作为期待值,而更新是由CAS确保原子性。Java中的无锁编程基本都是使用volatile结合UnsafeCAS确保线程安全,但是CAS无法处理ABA问题。而且CAS会带来更大的CPU开销。

线程安全计数器花费更多的时间是因为CAS失败导致的。

虽然只是介绍了AtomicInteger,但是其实其他的原子类内部实现的原理与AtomicInteger类似。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值