`

并发编程回顾:原子操作

 
阅读更多

原先多线程并发编程的学习笔记和代码整理一下贴上来。

---------------------------------

原子操作

一、什么是原子操作

原子操作是不能被线程调度机制中断的操作。一旦操作开始,它一定可以在可能发生的上下文切换之前(切换到其他线程执行)执行完毕。

原子操作可以应用于除long和double之外所有基本类型之上的简单操作。对于读取和写入除long和double之外的基本类型变量这样的操作,可以保证它们会被当作不可分的操作来操作内存。但64位的long和double读取和写入会被当作2个分离的32位操作来执行,这就产生一个读取和写入操作中间发生上下文切换,从而导致不同任务看到不正确的结果。

当定义long和double时,如果使用volatile,就会获得原子性。声明为volatile的域会立即写入主存中,而读取操作就发生在主存中。

多个线程同时访问某个域,那么这个域就应该是volatile的,否则这个域就应该只能由同步来访问。

但如果一个域的值依赖于它先前的值(例如计数器),或者一个域的值依赖于别的域值限制,volatile就无法正常工作。所以第一选择仍是使用synchronized关键字。

二、java.util.concurrent.atomic

从javase5开始,新加入了java.util.concurrent.atomic包, JDK文档中描述为:

类的小工具包,支持在单个变量上解除锁的线程安全编程。事实上,此包中的类可将volatile值、字段和数组元素的概念扩展到那些也提供原子条件更新操作的类。

在atomic包中提供了原子性变量类如AtomicBoolean、AtomicInteger、AtomicLong和AtomicReference等。

使用原子性变量类可以去除同步代码,但原子性变量类是被用来设计实现java.util.concurrent中的类,因此只有在特殊情况下再使用它们,一般常用于性能调优。

下面是一个对AtomicInteger的简单测试:

public class AtomicIntegerTest implements Runnable{
	private AtomicInteger ait = new AtomicInteger(0);
	
	private void increment(){
		ait.addAndGet(2);
	}
	private int get(){
		return ait.get();
	}
	@Override
	public void run() {
		while(true){
			//每个线程都循环累加
			increment();
			try{
				TimeUnit.MILLISECONDS.sleep(200);
			}catch(Exception e){
				e.printStackTrace();
			}
		}
	}
	public static void main(String[] args) throws Exception {
		ExecutorService es = Executors.newCachedThreadPool();
		AtomicIntegerTest task = new AtomicIntegerTest();
		for(int i=0;i<5;i++){
			es.submit(task);
		}
		TimeUnit.SECONDS.sleep(1);//保证5个任务都已经启动
		while(true){
			int value = task.get();
			if(value%2 != 0){
				System.out.println("error value!");
				System.exit(0);
			}else{
				System.out.println("value:"+task.get());
			}
			TimeUnit.MILLISECONDS.sleep(500);
		}
	}
}

上面的示例程序中,首先创建了一个线程池,开启5个线程,每个线程都循环对原子性变量AtomicInteger累加2,同时循环读取AtomicInteger的值,如果该值%2!=0的话,则说明出现了非原子性操作,程序停止,但本程序会一直运行下去打印出value的值。同时注意increment方法并没有加锁。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics