在Java中,i++这类的操作看起来只有一行,其实java 分成了三步去做
1、获取i值
2、计算i+1;
3、将结果存入i;
因此i++不是原子操作,非线程安全的,多线程访问的时候需要用到synchronized关键字保持线程同步。synchronized是悲观锁,在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,代价就是效率低下。
更加高效的锁就是乐观锁,所谓乐观锁就是不是每次都加锁,而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。
接下来要讲的Atomic则通过 CAS(Compare and Swap即比较并交换)的一种乐观锁机制在cpu层面实现的线程安全的操作接口,可以自动实现同步。
本文讲述Atomic系列的类的实现以及使用方法,其中包含:
基本类型:AtomicInteger、AtomicLong、AtomicBoolean;
数组类型:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
属性原子修改器(Updater):AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
引用类型:AtomicReference、AtomicReference的ABA实例、AtomicStampedRerence、AtomicMarkableReference;
一、基本类型使用
只需要看懂一个,其余的方法和使用都是大同小异的。
我们来看一下最简单的AtomicInteger有哪些常见的方法以及这些方法的作用。
- getAndAdd(int) 增加指定的数据,返回变化前的数据
- getAndDecrement() 减少1,返回减少前的数据
- getAndIncrement() 增加1,返回增加前的数据
- getAndSet(int) 设置指定的数据,返回设置前的数据
- addAndGet(int) 增加指定的数据后返回增加后的数据
- incrementAndGet() 增加1,返回增加后的值
- compareAndSet(int, int) 尝试新增后对比,若增加成功则返回true否则返回false
public class AtomicTest {
public final static AtomicInteger CONCURRENT_INTEGER = new AtomicInteger(1);
public static void main(String[] args) {
Thread[] threads=new Thread[10];
for(int i=0;i<10;i++){
threads[i]=new Thread(){
@Override
public void run() {
int num=CONCURRENT_INTEGER.addAndGet(2);
System.out.println("线程"+Thread.currentThread().getName()+"获取值后加2,结果为:"+num);
}
};
}
for(Thread t:threads ){
t.start();
}
for(Thread t:threads ){
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程"+Thread.currentThread().getName()+"最终获取结果为:"+CONCURRENT_INTEGER.get());
}
}
输出结果:
线程Thread-0获取值后加2,结果为:3
线程Thread-1获取值后加2,结果为:5
线程Thread-3获取值后加2,结果为:7
线程Thread-2获取值后加2,结果为:9
线程Thread-4获取值后加2,结果为:11
线程Thread-6获取值后加2,结果为:13
线程Thread-5获取值后加2,结果为:15
线程Thread-8获取值后加2,结果为:17
线程Thread-9获取值后加2,结果为:21
线程Thread-7获取值后加2,结果为:19
线程main结果为:21
虽然线程处于竞争,顺序错乱,但每条线程输出的结果是正确的。因为当一个线程在操作的时候,会对其它线程进行排斥,不用我们手动去使用synchronized实现互斥操作。AtomicLong和AtomicBoolean类似。
二、数组类型使用
先来看一下AtomicIntegerArray有哪些常见的方法以及这些方法的作用
- addAndGet(int, int) 执行加法,第一个参数为数组的下标,第二个参数为增加的数量,返回增加后的结果
- decrementAndGet(int) 参数为数组下标,将数组对应数字减少1,返回减少后的数据
- incrementAndGet(int) 参数为数组下标,将数组对应数字增加1,返回增加后的数据
- getAndAdd(int, int) 和addAndGet类似,区别是返回值是变化前的数据
- getAndDecrement(int) 和decrementAndGet类似,区别是返回变化前的数据
- getAndIncrement(int) 和incrementAndGet类似,区别是返回变化前的数据
- getAndSet(int, int) 将对应下标的数字设置为指定值,第二个参数为设置的值,返回是变化前的数据
- compareAndSet(int, int, int) 对比修改,参数1:数组下标,参数2:原始值,参数3,修改目标值,修改成功返回true否则false
public final static AtomicIntegerArray CONCURRENT_INTEGER_ARRAY = new AtomicIntegerArray(new int[]{35,99});
public static void main(String[] args) {
Thread[] threads=new Thread[10];
for(int i=0;i<10;i++){
threads[i]=new Thread(){
@Override
public void run() {
int result0=CONCURRENT_INTEGER_ARRAY.addAndGet(0,5);
int result1=CONCURRENT_INTEGER_ARRAY.addAndGet(1,7);
System.out.println("线程"+Thread.currentThread().getName()+"为数组元素[0]加5,结果为:"+result0+";为数组元素[1]加7,结果为:"+result1);
}
};
}
for(Thread t:threads ){
t.start();
}
for(Thread t:threads ){
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("======查看数组结果======");
for(int i = 0 ; i < CONCURRENT_INTEGER_ARRAY.length() ; i++) {
System.out.println("数组元素["+i+"]值为:"+CONCURRENT_INTEGER_ARRAY.get(i));
}
}
输出结果:
线程Thread-0为数组元素[0]加5,结果为:40;为数组元素[1]加7,结果为:106
线程Thread-2为数组元素[0]加5,结果为:45;为数组元素[1]加7,结果为:113
线程Thread-1为数组元素[0]加5,结果为:50;为数组元素[1]加7,结果为:120
线程Thread-3为数组元素[0]加5,结果为:55;为数组元素[1]加7,结果为:127
线程Thread-4为数组元素[0]加5,结果为:60;为数组元素[1]加7,结果为:134
线程Thread-5为数组元素[0]加5,结果为:65;为数组元素[1]加7,结果为:141
线程Thread-8为数组元素[0]加5,结果为:75;为数组元素[1]加7,结果为:155
线程Thread-6为数组元素[0]加5,结果为:70;为数组元素[1]加7,结果为:148
线程Thread-7为数组元素[0]加5,结果为:80;为数组元素[1]加7,结果为:162
线程Thread-9为数组元素[0]加5,结果为:85;为数组元素[1]加7,结果为:169
======查看数组结果======
数组元素[0]值为:85
数组元素[1]值为:169
三、属性原子修改器AtomicIntegerFieldUpdater
应用于复杂对象的属性操作
方法(说明上和AtomicInteger几乎一致,唯一的区别是第一个参数需要传入对象的引用)
定义修改器时,要注意
1、定义修改器时用AtomicIntegerFieldUpdater泛型类,需要指明类类型<T>
。
2、newUpdater工厂方法接收两个参数,分别是对象的类型,要修改的类属性名称。该方法返回修改器对象。
全部代码如下:
package JConcurrence.Study;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
class Dummy {
/* 必须指定为volatile类型,否则属性修改器不能工作 */
public volatile int number;
}
public class AtomicTest3 {
/*
* 1、定义修改器时用AtomicIntegerFieldUpdater泛型类,需要指明类类型<T>。
* 2、newUpdater工厂方法接收两个参数,分别是对象的类型,要修改的类属性名称。该方法返回修改器对象。
*/
public final static AtomicIntegerFieldUpdater<Dummy> FIELD_INTEGER_UPDATER = AtomicIntegerFieldUpdater
.newUpdater(Dummy.class, "number");
public static void main(String[] args) {
final Dummy dummy = new Dummy();
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread() {
@Override
public void run() {
int num = FIELD_INTEGER_UPDATER.addAndGet(dummy, 2);
System.out.println("线程" + Thread.currentThread().getName()
+ "获取值后加2,结果为:" + num);
}
};
}
for (Thread t : threads) {
t.start();
}
for (Thread t : threads) {
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程" + Thread.currentThread().getName()
+ "最终获取dummy.number结果为:" + dummy.number);
}
}
四、引用类型AtomicReference使用
先看常见的方法以及这些方法的作用
- boolean compareAndSet(V expect, V update)如果当前值
==
- V get() 获取当前值。
- V getAndSet(V newValue)以原子方式设置为给定值,并返回旧值。
- void lazySet(V newValue)最终设置为给定值。
- void set(V newValue)设置为给定值 String toString()返回当前值的字符串表示形式。
- boolean weakCompareAndSet(V expect, V update)如果当前值
==
预期值,则以原子方式将该值设置为给定的更新值。
很好理解,AtomicReference的用法和基本类型AtomicInteger的用法很类似。
AtomicReference作用于类,当然也可以作用在int值上。用法不解释了,只是这里要注意一个问题,对于引用类型,compareAndSet函数是如何判断期望值和当前值是否相等的。
先定义一个外部类:
class User {
public User(int id, String username) {
this.id = id;
this.username = username;
}
private int id;
private String username;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public String toString() {
return "User["+this.id+"]名称:"+this.username;
}
}
然后在主线程中调用:
public class AtomicTest4 {
public static void main(String[] args) {
User userA = new User(1,"叶开");
User userB = new User(2,"傅红雪");
/*定义并用userA初始化AtomicReference对象*/
AtomicReference<User> ATOMIC_REFERENCE = new AtomicReference<User>(userA);
User user=ATOMIC_REFERENCE.get();
ATOMIC_REFERENCE.compareAndSet(user, userB);
System.out.println(ATOMIC_REFERENCE.get());
}
}
功能很简单:ATOMIC_REFERENCE在初始化时,置入对象userA(叶开)
接下来调用方法compareAndSet()判断ATOMIC_REFERENCE持有对象如果是userA(叶开),就置为userB(傅红雪)
现在我们调整一下程序。在User user=ATOMIC_REFERENCE.get();下面增加一行修改User的名字
user.setUsername("李寻欢");
再次运行,发现还是输出“傅红雪”。AtomicReference认为对象并没有发生变化。
我们尝试重写equals方法再试一下。
为User对象增加equals()方法,只判断username是否相等。
在User类中重载方法equals
/*只比较名字是否一样,不判断ID是否一致*/
@Override
public boolean equals(Object obj) {
if(this==obj){
return true;
}
if(obj instanceof User){
User tempUser=(User)obj;
if(this.getUsername()==tempUser.getUsername()){
return true;
}else{
return false;
}
}else{
return false;
}
}
再次运行,还是输出“傅红雪”。
这么以来我们可以最终认定AtomicReference的比较机制是比较两个对象的地址,也就是采用“==”
比较,而不采用equals。