并发编程中级一:java并发包下的原子变量归纳总结

一.java并发包下的原子变量综述

在Java1.5中就引入了原子变量,它提供对单个变量的原子操作。当你在操作一个普通变量时,你在Java实现的每个操作,在程序编译时会被转换成几个机器能读懂的指令。例如,当你分配一个值给变量,在Java你只使用了一个指令,但是当你编译这个程序时,这个指令就被转换成多个JVM语言指令。这样子的话当你在操作多个线程且共享一个变量时,就会导致数据不一致的错误。
为了避免这样的问题,Java引入了原子变量。当一个线程正在操作一个原子变量时,即使其他线程也想要操作这个变量,类的实现中含有一个检查那步骤操作是否完成的机制。 基本上,操作获取变量的值,改变本地变量值,然后尝试以新值代替旧值。如果旧值还是一样,那么就改变它。如果不一样,方法再次开始操作。这个操作称为 Compare and Set(校对注:简称CAS,比较并交换的意思)。
原子变量不使用任何锁或者其他同步机制来保护它们的值的访问。他们的全部操作都是基于CAS操作。它保证几个线程可以同时操作一个原子对象也不会出现数据不一致的错误,并且它的性能比使用受同步机制保护的正常变量要好。

二.Atomic包介绍

在Atomic包里一共有12个类,四种原子更新方式,分别是原子更新基本类型,原子更新数组,原子更新引用和原子更新字段。Atomic包里的类基本都是使用Unsafe实现的包装类。

  • 1. 原子更新基本类型: AtomicInteger, AtomicLong, AtomicBoolean ;
  • 2. 原子更新数组: AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray ;
  • 3. 原子更新引用: AtomicReference, AtomicStampedReference, AtomicMarkableReference ;
  • 4. 原子更新字段: AtomicIntegerFieldUpdater, AtomicLongFieldUpdater, AtomicReferenceFieldUpdater
这些类存在的目的是对相应的数据进行原子操作。所谓原子操作,是指操作过程不会被中断,保证数据操作是以原子方式进行的。

三.原子更新基本类型

因为大体相同,以一个作为示例.
AtomicLong是作用是对长整形进行原子操作。
在32位操作系统中,64位的long 和 double 变量由于会被JVM当作两个分离的32位来进行操作,所以不具有原子性。而使用AtomicLong能让long的操作保持原子型。

3.1 函数列表

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 构造函数
AtomicLong()
// 创建值为initialValue的AtomicLong对象
AtomicLong( long initialValue)
// 以原子方式设置当前值为newValue。
final void set( long newValue)
// 获取当前值
final long get()
// 以原子方式将当前值减 1,并返回减1后的值。等价于“--num”
final long decrementAndGet()
// 以原子方式将当前值减 1,并返回减1前的值。等价于“num--”
final long getAndDecrement()
// 以原子方式将当前值加 1,并返回加1后的值。等价于“++num”
final long incrementAndGet()
// 以原子方式将当前值加 1,并返回加1前的值。等价于“num++”
final long getAndIncrement()   
// 以原子方式将delta与当前值相加,并返回相加后的值。
final long addAndGet( long delta)
// 以原子方式将delta添加到当前值,并返回相加前的值。
final long getAndAdd( long delta)
// 如果当前值 == expect,则以原子方式将该值设置为update。成功返回true,否则返回false,并且不修改原值。
final boolean compareAndSet( long expect, long update)
// 以原子方式设置当前值为newValue,并返回旧值。
final long getAndSet( long newValue)
// 返回当前值对应的int值
int intValue()
// 获取当前值对应的long值
long longValue()   
// 以 float 形式返回当前值
float floatValue()   
// 以 double 形式返回当前值
double doubleValue()   
// 最后设置为给定值。延时设置变量值,这个等价于set()方法,但是由于字段是volatile类型的,因此次字段的修改会比普通字段(非volatile字段)有稍微的性能延时(尽管可以忽略),所以如果不是想立即读取设置的新值,允许在“后台”修改值,那么此方法就很有用。如果还是难以理解,这里就类似于启动一个后台线程如执行修改新值的任务,原线程就不等待修改结果立即返回(这种解释其实是不正确的,但是可以这么理解)。
final void lazySet( long newValue)
// 如果当前值 == 预期值,则以原子方式将该设置为给定的更新值。JSR规范中说:以原子方式读取和有条件地写入变量但不 创建任何 happen-before 排序,因此不提供与除 weakCompareAndSet 目标外任何变量以前或后续读取或写入操作有关的任何保证。大意就是说调用weakCompareAndSet时并不能保证不存在happen-before的发生(也就是可能存在指令重排序导致此操作失败)。但是从Java源码来看,其实此方法并没有实现JSR规范的要求,最后效果和compareAndSet是等效的,都调用了unsafe.compareAndSwapInt()完成操作。
final boolean weakCompareAndSet( long expect, long update)

常用方法如下: 

int addAndGet(int delta) :以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果
boolean compareAndSet(int expect, int update) :如果输入的数值等于预期值,则以原子方式将该值设置为输入的值。
int getAndIncrement():以原子方式将当前值加1,注意:这里返回的是自增前的值。
void lazySet(int newValue):见2.3.
int getAndSet(int newValue):以原子方式设置为newValue的值,并返回旧值。

3.2 AtomicLong.lazySet是如何工作的?

为一个AtomicLong对象设置一个值,jvm会确保其他线程读取到最新值,原子类和voliatile变量也是一样的,这是由依赖于硬件的系统指令(如x86的xchg)实现的。lazySet却是无法保证这一点的方法,所以其他线程在之后的一小段时间里还是可以读到旧的值。
在多核处理器下,内存以及cpu缓存的读和写常常是顺序执行的,所以在多个cpu缓存之间同步一个内存值的代价是很昂贵的。这样做是提高了性能.
大多数的原子类,比如AtomicLong本质上都是一个Unsafe和一个volatile Long变量的包装类.

3.3 其他基本类型怎么办?

Atomic包提供了三种基本类型的原子更新,但是Java的基本类型里还有char,float和double等。那么问题来了,如何原子的更新其他的基本类型呢?Atomic包里的类基本都是使用Unsafe实现的,让我们一起看下Unsafe的源码,发现Unsafe只提供了三种CAS方法,compareAndSwapObject,compareAndSwapInt和compareAndSwapLong,再看AtomicBoolean源码,发现其是先把Boolean转换成整型,再使用compareAndSwapInt进行CAS,所以原子更新double也可以用类似的思路来实现。

3.4 AtomicLong 使用示例

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package org.credo.jdk.thread.atomic;
 
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
 
public class TestAtomicLong implements Runnable
{
     public static AtomicLong atomicLong = new AtomicLong(1000L);
 
     private String operator; // 操作人
     private long number; // 操作数额
     private Lock lock;
 
     public TestAtomicLong(String operator, long number, Lock lock)
     {
         this .operator = operator;
         this .number = number;
         this .lock = lock;
     }
 
     @Override
     public void run()
     {
         long result = atomicLong.addAndGet(number);
         System.out.println(Thread.currentThread().getName() + ":   " + operator + "执行了" + number + ",当前余额:" + result);
         System.out.println(Thread.currentThread().getName() + ":   " + operator + "+1-->" + atomicLong.addAndGet( 1 ));
         System.out.println(Thread.currentThread().getName() + ":   " + operator + atomicLong.toString());
     }
 
     public static void main(String[] args)
     {
         ExecutorService pool = Executors.newFixedThreadPool( 2 );
         Lock lock = new ReentrantLock( false );
         Runnable t1 = new TestAtomicLong( "张三" , 100 , lock);
         Runnable t2 = new TestAtomicLong( "李四" , 200 , lock);
         Runnable t3 = new TestAtomicLong( "王五" , 1000 , lock);
         pool.execute(t1);
         pool.execute(t2);
         pool.execute(t3);
         pool.shutdown();
     }
 
}

运行结果:

pool-1-thread-2:   李四执行了200,当前余额:1300
pool-1-thread-1:   张三执行了100,当前余额:1100
pool-1-thread-2:   李四+1-->1301
pool-1-thread-1:   张三+1-->1302
pool-1-thread-2:   李四1302
pool-1-thread-1:   张三1302
pool-1-thread-2:   王五执行了1000,当前余额:2302
pool-1-thread-2:   王五+1-->2303
pool-1-thread-2:   王五2303

结果是我们预见的2303.

如果我们修改AtomicLong atomicLong 为普通的Long类型[public static Long atomicLong = new Long(1000L);],会发送什么?!同时我们修改RUN方法

?
1
2
3
4
5
6
7
8
@Override
     public void run()
     {
         long result = atomicLong+(number);
         System.out.println(Thread.currentThread().getName() + ":   " + operator + "执行了" + number + ",当前余额:" + result);
         //System.out.println(Thread.currentThread().getName() + ":   " + operator + "+1-->" + atomicLong.addAndGet(1));
         System.out.println(Thread.currentThread().getName() + ":   " + operator + atomicLong.toString());
     }

执行结果,完全是错乱的:

pool-1-thread-1:   张三1000
pool-1-thread-1:   王五执行了1000,当前余额:2000
pool-1-thread-1:   王五1000
pool-1-thread-2:   李四执行了200,当前余额:1200
pool-1-thread-2:   李四1000

四:原子更新数组

AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray类进一步扩展了原子操作,对这些类型的数组提供了支持。这些类在为其数组元素提供 volatile 访问语义方面也引人注目,这对于普通数组来说是不受支持的。他们内部并不是像AtomicInteger一样维持一个volatile变量,而是全部由native方法实现,

4.1 AtomicLongArray函数

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 创建给定长度的新 AtomicLongArray。
AtomicLongArray( int length)
// 创建与给定数组具有相同长度的新 AtomicLongArray,并从给定数组复制其所有元素。
AtomicLongArray( long [] array)
// 以原子方式将给定值增加到索引 i 的元素。
long addAndGet( int i, long delta)
// 如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。
boolean compareAndSet( int i, long expect, long update)
// 以原子方式将索引 i 的元素减1。
long decrementAndGet( int i)
// 获取位置 i 的当前值。
long get( int i)
// 以原子方式将给定值与索引 i 的元素相加。
long getAndAdd( int i, long delta)
// 以原子方式将索引 i 的元素减 1。
long getAndDecrement( int i)
// 以原子方式将索引 i 的元素加 1。
long getAndIncrement( int i)
// 以原子方式将位置 i 的元素设置为给定值,并返回旧值。
long getAndSet( int i, long newValue)
// 以原子方式将索引 i 的元素加1。
long incrementAndGet( int i)
// 最终将位置 i 的元素设置为给定值。
void lazySet( int i, long newValue)
// 返回该数组的长度。
int length()
// 将位置 i 的元素设置为给定值。
void set( int i, long newValue)
// 返回数组当前值的字符串表示形式。
String toString()
// 如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。
boolean    weakCompareAndSet( int i, long expect, long update)



4.2 AtomicIntegerArray的实现片断:

?
1
2
3
4
5
6
7
8
9
10
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);
}



4.3 AtimicLongArray示例

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public static AtomicLongArray atomicLongArray = new AtomicLongArray( new long []{ 0 , 0 , 0 });
 
 
private String operator; // 操作人
private long number; // 操作数额
 
 
public TestAtomicLong(String operator, long number)
{
this .operator = operator;
this .number = number;
}
 
 
@Override
public void run()
{
atomicLongArray.addAndGet( 0 , number);
atomicLongArray.getAndIncrement( 0 );
atomicLongArray.set( 2 , number);
System.out.println(Thread.currentThread().getName() + ":   " + operator + "执行了" + number + ",当前:" + atomicLongArray.toString());
}
 
 
public static void main(String[] args)
{
ExecutorService pool = Executors.newFixedThreadPool( 2 );
Runnable t1 = new TestAtomicLong( "张三" , 1 );
Runnable t2 = new TestAtomicLong( "李四" , 2 );
Runnable t3 = new TestAtomicLong( "王五" , 3 );
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
pool.shutdown();
}
 
 
结果
pool- 1 -thread- 1 :   张三执行了 1 ,当前:[ 5 , 0 , 2 ]
pool- 1 -thread- 2 :   李四执行了 2 ,当前:[ 5 , 0 , 2 ]
pool- 1 -thread- 1 :   王五执行了 3 ,当前:[ 9 , 0 , 3 ]


五.原子更新引用

AtomicReference是作用是对"对象"进行原子操作。AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。




六.原子更新字段

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 。
后续将继续修补中.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值