概述
Java中关于原子操作和volatile关键字
研究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[N];
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") ;
}
}
}
java.util.concurrent.atomic原子操作类包
这个包里面提供了一组原子变量类。其基本的特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个另一个线程进入,这只是一种逻辑上的理解。实际上是借助硬件的相关指令来实现的,不会阻塞线程(或者说只是在硬件级别上阻塞了)。可以对基本数据、数组中的基本数据、对类中的基本数据进行操作。原子变量类相当于一种泛化的volatile变量,能够支持原子的和有条件的读-改-写操作。
java.util.concurrent.atomic中的类可以分成4组:
- 标量类(Scalar):AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
- 数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
- 更新器类:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
- 复合变量类:AtomicMarkableReference,AtomicStampedReference
第一组AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference这四种基本类型用来处理布尔,整数,长整数,对象四种数据,其内部实现不是简单的使用synchronized,而是一个更为高效的方式CAS (compare and swap) + volatile和native方法,从而避免了synchronized的高开销,执行效率大为提升。如AtomicInteger的实现片断为:
- private static final Unsafe unsafe = Unsafe.getUnsafe();
- private volatile int value;
- public final int get() {
- return value;
- }
- public final void set(int newValue) {
- value = newValue;
- }
- public final boolean compareAndSet(int expect, int update) {
- return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
- }
- 构造函数(两个构造函数)
- 默认的构造函数:初始化的数据分别是false,0,0,null
- 带参构造函数:参数为初始化的数据
- set( )和get( )方法:可以原子地设定和获取atomic的数据。类似于volatile,保证数据会在主存中设置或读取
- void set()和void lazySet():set设置为给定值,直接修改原始值;lazySet延时设置变量值,这个等价于set()方法,但是由于字段是volatile类型的,因此次字段的修改会比普通字段(非volatile字段)有稍微的性能延时(尽管可以忽略),所以如果不是想立即读取设置的新值,允许在“后台”修改值,那么此方法就很有用。
- getAndSet( )方法
- 原子的将变量设定为新数据,同时返回先前的旧数据
- 其本质是get( )操作,然后做set( )操作。尽管这2个操作都是atomic,但是他们合并在一起的时候,就不是atomic。在Java的源程序的级别上,如果不依赖synchronized的机制来完成这个工作,是不可能的。只有依靠native方法????才可以。
- public final int getAndSet(int newValue) {
- for (;;) {
- int current = get();
- if (compareAndSet(current, newValue))
- return current;
- }
- }
- compareAndSet( ) 和weakCompareAndSet( )方法
- 这 两个方法都是conditional modifier方法。这2个方法接受2个参数,一个是期望数据(expected),一个是新数据(new);如果atomic里面的数据和期望数据一 致,则将新数据设定给atomic的数据,返回true,表明成功;否则就不设定,并返回false。JSR规范中说:以原子方式读取和有条件地写入变量但不 创建任何 happen-before 排序,因此不提供与除 weakCompareAndSet 目标外任何变量以前或后续读取或写入操作有关的任何保证。大意就是说调用weakCompareAndSet时并不能保证不存在happen- before?????的发生(也就是可能存在指令重排序导致此操作失败)。但是从Java源码来看,其实此方法并没有实现JSR规范的要求,最后效果和 compareAndSet是等效的,都调用了unsafe.compareAndSwapInt()完成操作。
- public final boolean compareAndSet(int expect, int update) {
- return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
- }
- public final boolean weakCompareAndSet(int expect, int update) {
- return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
- }
- 对于 AtomicInteger、AtomicLong还提供了一些特别的方法。
getAndIncrement( ):以原子方式将当前值加 1,相当于线程安全的i++操作。
incrementAndGet( ):以原子方式将当前值加 1, 相当于线程安全的++i操作。
getAndDecrement( ):以原子方式将当前值减 1, 相当于线程安全的i--操作。
decrementAndGet ( ):以原子方式将当前值减 1,相当于线程安全的--i操作。
addAndGet( ): 以原子方式将给定值与当前值相加, 实际上就是等于线程安全的i =i+delta操作。
getAndAdd( ):以原子方式将给定值与当前值相加, 相当于线程安全的t=i;i+=delta;return t;操作。
以实现一些加法,减法原子操作。(注意 --i、++i不是原子操作,其中包含有3个操作步骤:第一步,读取i;第二步,加1或减1;第三步:写回内存)
使用AtomicReference创建线程安全的堆栈
- package thread;
- import java.util.concurrent.atomic.AtomicReference;
- public class ConcurrentStack<T> {
- private AtomicReference<Node<T>> stacks = new AtomicReference<Node<T>>();
- public T push(T e) {
- Node<T> oldNode, newNode;
- for (;;) { // 这里的处理非常的特别,也是必须如此的。
- oldNode = stacks.get();
- newNode = new Node<T>(e, oldNode);
- if (stacks.compareAndSet(oldNode, newNode)) {
- return e;
- }
- }
- }
- public T pop() {
- Node<T> oldNode, newNode;
- for (;;) {
- oldNode = stacks.get();
- newNode = oldNode.next;
- if (stacks.compareAndSet(oldNode, newNode)) {
- return oldNode.object;
- }
- }
- }
- private static final class Node<T> {
- private T object;
- private Node<T> next;
- private Node(T object, Node<T> next) {
- this.object = object;
- this.next = next;
- }
- }
- }
虽然原子的标量类扩展了Number类,但并没有扩展一些基本类型的包装类,如Integer或Long,事实上他们也不能扩展:基本类型的包装类是不可以修改的,而原子变量类是可以修改的。在原子变量类中没有重新定义hashCode或equals方法,每个实例都是不同的,他们也不宜用做基于散列容器中的键值。
第二组AtomicIntegerArray,AtomicLongArray还有AtomicReferenceArray类进一步扩展了原子操作,对这些类型的数组提供了支持。这些类在为其数组元素提供 volatile 访问语义方面也引人注目,这对于普通数组来说是不受支持的。
他们内部并不是像AtomicInteger一样维持一个valatile变量,而是全部由native方法实现,如下
AtomicIntegerArray的实现片断:
- private static final Unsafe unsafe = Unsafe.getUnsafe();
- private static final int base = unsafe.arrayBaseOffset(int[].class);
- private static final int scale = unsafe.arrayIndexScale(int[].class);
- private final int[] array;
- public final int get(int i) {
- return unsafe.getIntVolatile(array, rawIndex(i));
- }
- public final void set(int i, int newValue) {
- unsafe.putIntVolatile(array, rawIndex(i), newValue);
- }
第三组AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater基于反射的实用工具,可以对指定类的指定 volatile
字段进行原子更新。API非常简单,但是也是有一些约束:
(1)字段必须是volatile类型的
(2)字段的描述类型(修饰符public/protected/default/private)是与调用者与操作对象字段的关系一致。也就是说 调用者能够直接操作对象字段,那么就可以反射进行原子操作。但是对于父类的字段,子类是不能直接操作的,尽管子类可以访问父类的字段。
(3)只能是实例变量,不能是类变量,也就是说不能加static关键字。
(4)只能是可修改变量,不能使final变量,因为final的语义就是不可修改。实际上final的语义和volatile是有冲突的,这两个关键字不能同时存在。
(5)对于AtomicIntegerFieldUpdater 和AtomicLongFieldUpdater 只能修改int/long类型的字段,不能修改其包装类型(Integer/Long)。如果要修改包装类型就需要使用AtomicReferenceFieldUpdater 。
netty5.0中类ChannelOutboundBuffer统计发送的字节总数,由于使用volatile变量已经不能满足,所以使用AtomicIntegerFieldUpdater 来实现的,看下面代码:
- //定义
- private static final AtomicLongFieldUpdater<ChannelOutboundBuffer> TOTAL_PENDING_SIZE_UPDATER =
- AtomicLongFieldUpdater.newUpdater(ChannelOutboundBuffer.class, "totalPendingSize");
- private volatile long totalPendingSize;
- //使用
- long oldValue = totalPendingSize;
- long newWriteBufferSize = oldValue + size;
- while (!TOTAL_PENDING_SIZE_UPDATER.compareAndSet(this, oldValue, newWriteBufferSize)) {
- oldValue = totalPendingSize;
- newWriteBufferSize = oldValue + size;
- }
最后
以上就是还单身草丛为你收集整理的java.util.concurrent.atomic原子操作类包 Java中关于原子操作和volatile关键字 研究ThreadPoolExecutor的时候,发现其中大量使用了volatile变量。不知为何,因此做了一番查找,研究: 其中借鉴了很多网上资料。 在了解volatile变量作用前,先需要明白一些概念: 什么是原子操作? 所谓原子操作,就是"不可中断的一个或一系列操作" , 在确认一个操作是原子的情况下,多线程环境里面,我们可以避免仅仅为保护这个操作在外围加上性能昂贵的锁的全部内容,希望文章能够帮你解决java.util.concurrent.atomic原子操作类包 Java中关于原子操作和volatile关键字 研究ThreadPoolExecutor的时候,发现其中大量使用了volatile变量。不知为何,因此做了一番查找,研究: 其中借鉴了很多网上资料。 在了解volatile变量作用前,先需要明白一些概念: 什么是原子操作? 所谓原子操作,就是"不可中断的一个或一系列操作" , 在确认一个操作是原子的情况下,多线程环境里面,我们可以避免仅仅为保护这个操作在外围加上性能昂贵的锁所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复