您的位置:首页 > 编程语言 > Java开发

Java中关于原子操作和volatile关键字

2015-03-20 09:36 357 查看
原帖:http://rwl6813021.javaeye.com/blog/349169

研究ThreadPoolExecutor的时候,发现其中大量使用了volatile变量。不知为何,因此做了一番查找,研究: 其中借鉴了很多网上资料。 在了解volatile变量作用前,先需要明白一些概念:

什么是原子操作?

所谓原子操作,就是"不可中断的一个或一系列操作" , 在确认一个操作是原子的情况下,多线程环境里面,我们可以避免仅仅为保护这个操作在外围加上性能昂贵的锁,甚至借助于原子操作,我们可以实现互斥锁。 很多操作系统都为int类型提供了+-赋值的原子操作版本,比如 NT 提供了 InterlockedExchange 等API, Linux/UNIX也提供了atomic_set 等函数。

关于java中的原子性?

原子性可以应用于除long和double之外的所有基本类型之上的“简单操作”。对于读取和写入出long double之外的基本类型变量这样的操作,可以保证它们会被当作不可分(原子)的操作来操作。 因为JVM的版本和其它的问题,其它的很多操作就不好说了,比如说++操作在C++中是原子操作,但在Java中就不好说了。 另外,Java提供了AtomicInteger等原子类。再就是用原子性来控制并发比较麻烦,也容易出问题。

volatile原理是什么?

Java中volatile关键字原义是“不稳定、变化”的意思

使用volatile和不使用volatile的区别在于JVM内存主存和线程工作内存的同步之上。volatile保证变量在线程工作内存和主存之间一致。

其实是告诉处理器, 不要将我放入工作内存, 请直接在主存操作我.

接下来是测试 :(通过测试能更好的发现和分析问题)

申明了几种整形的变量,开启100个线程同时对这些变量进行++操作,发现结果差异很大:

>>Execute End:

>>Atomic: 100000

>>VInteger: 38790

>>Integer: 68749

>>Source i: 99205

>>Source Vi: 99286

也就是说除了Atomic,其他的都是错误的。

我们通过一些疑问,来解释一下。

1:为什么会产生错误的数据?

多线程引起的,因为对于多线程同时操作一个整型变量在大并发操作的情况下无法做到同步,而Atom提供了很多针对此类线程安全问题的解决方案,因此解决了同时读写操作的问题。

2:为什么会造成同步问题?

Java多线程在对变量进行操作的时候,实际上是每个线程会单独分配一个针对i值的拷贝(独立内存区域),但是申明的i值确是在主内存区域中,当对i值修改完毕后,线程会将自己内存区域块中的i值拷贝到主内存区域中,因此有可能每个线程拿到的i值是不一样的,从而出现了同步问题。

3:为什么使用volatile修饰integer变量后,还是不行?

因为volatile仅仅只是解决了存储的问题,即i值只是保留在了一个内存区域中,但是i++这个操作,涉及到获取i值、修改i值、存储i值(i=i+1),这里的volatile只是解决了存储i值得问题,至于获取和修改i值,确是没有做到同步。

4:既然不能做到同步,那为什么还要用volatile这种修饰符?

主要的一个原因是方便,因为只需添加一个修饰符即可,而无需做对象加锁、解锁这么麻烦的操作。但是本人不推荐使用这种机制,因为比较容易出问题(脏数据),而且也保证不了同步。

5:那到底如何解决这样的问题?

第一种:采用同步synchronized解决,这样虽然解决了问题,但是也降低了系统的性能。

第二种:采用原子性数据Atomic变量,这是从JDK1.5开始才存在的针对原子性的解决方案,这种方案也是目前比较好的解决方案了。

6:Atomic的实现基本原理?

首先Atomic中的变量是申明为了volatile变量的,这样就保证的变量的存储和读取是一致的,都是来自同一个内存块,然后Atomic提供了getAndIncrement方法,该方法对变量的++操作进行了封装,并提供了compareAndSet方法,来完成对单个变量的加锁和解锁操作,方法中用到了一个UnSafe的对象,现在还不知道这个UnSafe的工作原理(似乎没有公开源代码)。Atomic虽然解决了同步的问题,但是性能上面还是会有所损失,不过影响不大,网上有针对这方面的测试,大概50million的操作对比是250ms
: 850ms,对于大部分的高性能应用,应该还是够的了。

package qflag.ucstar.test.thread;

import java.util.concurrent.atomic.AtomicInteger;

/**

* 测试原子性的同步

* @author polarbear 2009-3-14

*

*/

public class TestAtomic {

public static AtomicInteger astom_i = new AtomicInteger();

public static volatile Integer v_integer_i = 0;

public static volatile int v_i = 0;

public static Integer integer_i = 0;

public static int i = 0;

public static int endThread = 0;

public static void main(String[] args) {

new TestAtomic().testAtomic();

}

public void testAtomic() {

for(int i=0; i<100; i++) {

new Thread(new IntegerTestThread()).start();

}

try {

for(;;) {

Thread.sleep(500);

if(TestAtomic.endThread == 100) {

System.out.println(">>Execute End:");

System.out.println(">>Atomic: /t"+TestAtomic.astom_i);

System.out.println(">>VInteger: /t"+TestAtomic.v_integer_i);

System.out.println(">>Integer: /t"+TestAtomic.integer_i);

System.out.println(">>Source i: /t"+TestAtomic.i);

System.out.println(">>Source Vi: /t"+TestAtomic.v_i);

break;

}

}

} catch (Exception e) {

e.printStackTrace();

}

}

}

class IntegerTestThread implements Runnable {

public void run() {

int x = 0;

while(x<1000) {

TestAtomic.astom_i.incrementAndGet();

TestAtomic.v_integer_i++;

TestAtomic.integer_i++;

TestAtomic.i++;

TestAtomic.v_i++;

x++;

}

++TestAtomic.endThread; //貌似很无敌!难道是原子性的吗?

}

}


-----------------------------------------xx-----------------------------------xx-----------------------------------------------

本人继续补充:

除了TestAtomic.endThread,其他的变量都被忽略了。具体解释可参见注释。

import java.util.concurrent.atomic.AtomicInteger;

import java.io.*;

/**

* 测试原子性的同步

* @author pyc 2009-3-29

*

*/

public class TestAtomic {

public static final int N=10;

public static final int M=10000;

public static int perfect_result=M*N;

public static int endThread = 0;

private PrintWriter out;//将信息输入至文本"out.txt",因为控制台buffer可能不够.


public TestAtomic() throws IOException

{

out =new PrintWriter(

new BufferedWriter(

new FileWriter("out.txt")));

}

public static void main(String[] args) {

try{

new TestAtomic().testAtomic();

}catch(Exception e){

System.out.println(e.getMessage());

}

System.out.println("OK./nStatistical report:");

System.out.println("Covered by "+(perfect_result-endThread)+" times.");

}

public void testAtomic() {

Thread[] td=new Thread
;

for(int i=0; i<N; i++) {

td[i]=new Thread(new IntegerTestThread(i+1));

}

for(int i=0; i<N; i++) {

td[i].start();

out.println((i+1)+" go..") ; //此处如果run()方法代码少,立即可观察到complete完成信息。

}

try {

long temp=0; //存放了上次的endTread值。

int count=1000; //如果temp值超过一千次的重复就可以认为结束程序。

for(;;) {

//Thread.sleep(1); //有可能main线程运行过快,可以调节采样的频率。

if(TestAtomic.endThread == perfect_result) {

out.println("==============/r/nPerfect!/r/n=============="); //完美匹配!

break;

}

if(temp==TestAtomic.endThread){

out.println("Equal!!");//有重复,有可能是所有线程运行结束时的重复,也有可能是main线程采样过快。

count--;//倒计时中。。。

}

else {

temp=TestAtomic.endThread;//给temp赋新值。

count=1000;//重新设置倒计时。

}

out.println("endThread = "+TestAtomic.endThread);//在此处有几率可观察当前的endThread值比上次要少。

//这是关键之处!

if(count<=0)

{

out.println("/r/nI'll be crazy if I wait for that once again!/r/nFailed, OMG!+_+");

break;

}

}

out.close();

}catch(Exception e) {

e.printStackTrace();

}

}

class IntegerTestThread implements Runnable {

private int id;

public IntegerTestThread(int i){

this.id=i;

}

public void run() {

int i=M;//充分保证线程重叠运行

while(i>0){

try{

//Thread.sleep((int)(10*Math.random()));//设置睡眠时间,从而尽可能使线程重叠运行。

}catch(Exception e){

++TestAtomic.endThread;//测试该语句的“原子”性。其实做完实验,我们知道,++i,i++, i=i+1一样都不能保证原子性。

//我们可以从最终的endThread值是不是等于M*N得知。

i--;

}

out.println("************/r/n"+id+" has Completed!/r/n************/r/n") ;

}

}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: