Java并发编程-原子变量

原子变量最主要的一个特点就是所有的操作都是原子的,关于原子变量的介绍,主要涉及以下内容:

原子变量的基本概念

通过AtomicInteger了解原子变量的基本使用

通过AtomicInteger了解原子变量的基本原理

AtomicReference的基本使用

使用FieldUpdater操作非原子变量的字段属性

经典的ABA问题的解决

一、原子变量的概念

原子变量保证了该变量的所有操作都是原子的,不会因为多线程的同时访问而导致脏数据的读取问题。我们先看一段synchronized关键字保证变量原子性的代码:

public class Demo {
	private int count;
	
	public synchronized void addCount(){
		this.count++;
	}
}

 简单的count++操作,线程对象首先需要获取到类实例的对象锁,然后完成自增操作,最后释放对象锁。整个过程中,无论是获取锁还是释放锁都是相当消耗成本的,一旦不能获取到锁,还需要阻塞当前线程等等。

 对于这种情况,我们可以将count变量声明成原子变量,那么对于count的自增操作都可以以原子的方式进行,就不存在脏数据的读取了。

Java给我们提供了以下几种原子类型:

  •       AtomicInteger和AtomicIntegerArray:基于Integer类型
  •       AtomicBoolean:基于Boolean类型
  •       AtomicLong和AtomicLongArray:基于Long类型
  •       AtomicReference和AtomicReferenceArray:基于引用类型

 在本文的内容中,将主要介绍AtomicInteger和AtomicReference两种类型,AtomicBoolean和AtomicLong的使用和内部实现原理几乎和AtomicInteger一样。

二、AtomicInteger的基本使用

我们首先看下它的底层实现源码,先看它的两个构造函数:

如果我们通过构造函数构造AtomicInteger原子变量的时候,如果指定一个int的参数,那么该原子变量的值就会被赋值,否则就是默认的数值0。

当然也有获取和设置这个value值的方法

当然,这两个方法并不是原子的,所以一般也很少使用,而以下的这些基于原子操作的方法则相对使用的频繁,至于它们的具体实现是怎样的,我们将在本文后面进行简单的学习。

除了以上还有一些反向的方法,比如:先自增在获取值的等,他们都是基于原子操作的。

下面我们使用原子变量实现一个计算器的列子:

//定义一个线程类
public class MyThread extends Thread{
	//AtomicInteger原子变量对象
	 public static AtomicInteger value = new AtomicInteger();
	 //private static int value ;
	
	public void run(){
		try {
			Thread.sleep((int)(Math.random()*1000));
			//int a = value++; //原子自增
			value.incrementAndGet(); 
			//System.out.println(a); //原子自增
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}
	
	//main方法中创建100条线程并让他们启动
	public static void main(String[] args)throws InterruptedException {
		Thread [] threads = new Thread[100]; //创建100条线程
			for(int i=0;i<100;i++){
				threads[i] = new MyThread();
				threads[i].start(); //启动线程
			}
			
			for(int j=0;j<100;j++){
				threads[j].join();
			}
			
			//Thread.sleep(2000);
			System.out.println("value:"+MyThread.value);
	}
}

我们发现多次运行都会得到相同的结果:

使用原子变量要比使用synchronized要简洁的多并且效率也相对较高。

三、AtomicInteger的内部基本原理

   AtomicIntger内部主要操作的是value字段,这个字段就是保存原子变量的值。

只要我们接触到了并发编程就会知道,并发编程中很多底层都实现了CAS机制,原子变量也是一样,我们看下这个方法。

我们主要学习下incrementAndGet的方法实现,在1.7之前是这样实现的。

  方法体是一个死循环,current获取到当前原子变量中的值,由于value被修饰volatile,所以不存在内存可见性问题,数据一定是最新的。然后current加一后赋值给next,调用我们的CAS原子操作判断value是否被别的线程修改过,如果还是原来的值,那么将next的值赋值给value并返回next,否则重新获取当前value的值,再次进行判断,直到操作完成。

  incrementAndGet方法的一个很核心的思想是,在加一之前先去看看value的值是多少,真正加的时候再去看一下,如果发现变了,不操作数据,否则为value加一。

      但是在jdk1.8以后,做了一些优化,但基本思想还是没变。

四、AtomicReference的基本使用

对于一些自定义类或者字符串等这些引用类型,Java并发包也提供了原子变量的接口支持,AtomicReference内部使用泛型来实现的。

AtomicReference中少了一些自增自减的操作,但是对于value的修改依然是原子的。

五、使用FieldUpdater操作非原子变量的字段属性

FieldUpdater允许我们不必将字段设置为原子变量,利用反射直接以原子方式操作字段。

public class AtomicIntegerTest {
	
	  private volatile int count;

	    public int getCount() {
	        return count;
	    }

	    public void addCount(){
	    	//通过反射机制实现原子操作的方式
	        AtomicIntegerFieldUpdater<AtomicIntegerTest> updater  = 
	        		AtomicIntegerFieldUpdater.newUpdater(AtomicIntegerTest.class,"count");
	        updater.getAndIncrement(this);
	    }
}

然后我们创建一百个线程随机调用同一个Counter对象的addCount方法,无论运行多少次,结果都是一百。这种方式实现的原子操作,对于被操作的变量不需要被包装成原子变量,但是却可以直接以原子方式操作它的数值。

六、经典的ABA问题

我们的原子变量都依赖一个核心的方法,那就是CAS。这个方法最核心的思想就是,更改变量值之前先获取该变量当前最新的值,然后在实际更改的时候再次获取该变量的值,如果没有被修改,那么进行更改,否则循环上述操作直至更改操作完成。假如一个线程想要对变量count进行修改,实际操作之前获取count的值为A,此时来了一个线程将count值修改为B,又来一个线程获取count的值为B并将count修改为A,此时第一个线程全然不知道count的值已经被修改两次了,虽然值还是A,但是实际上数据已经是脏的。

      这就是典型的ABA问题,一个解决办法是,对count的每次操作都记录下当前的一个时间戳,这样当我们原子操作count之前,不仅查看count的最新数值,还记录下该count的时间戳,在实际操作的时候,只有在count的数值和时间戳都没有被更改的情况之下才完成修改操作。

public static void main(String[] args){ 

int count=0; 

int stamp = 1; 

AtomicStampedReference reference = new AtomicStampedReference(count,stamp);

int next = count++; 

reference.compareAndSet(count, next, stamp, stamp); 

}

      AtomicStampedReference 的CAS方法要求传入四个参数,该方法的内部会同时比较count和stamp,只有这两个值都没有发生改变的前提下,CAS才会修改count的值。

      上述我们介绍了有关原子变量的最基本内容,最后我们比较下原子变量和synchronized关键字的区别。

      从思维模式上看,原子变量代表一种乐观的非阻塞式思维,它假定没有别人会和我同时操作某个变量,于是在实际修改变量的值的之前不会锁定该变量,但是修改变量的时候是使用CAS进行的,一旦发现冲突,继续尝试直到成功修改该变量。

      而synchronized关键字则是一种悲观的阻塞式思维,它认为所有人都会和我同时来操作某个变量,于是在将要操作该变量之前会加锁来锁定该变量,进而继续操作该变量。

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sail-Wang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值