一、原子整数
J.U.C 并发包提供了:
- AtomicBoolean
- AtomicInteger
- AtomicLong
这几个类保证对包装的值的操作是原子操作,故线程安全。
以 AtomicInteger 为例
//获取并自增(i=0,结果i=1,返回0),类似i++
System.out.println(i.getAndIncrement());
//自增并获取(i=1,结果i=2,返回2),类似++i
System.out.println(i.incrementAndGet());
//自减并获取(i=2,结果i=1,返回1),类似--i
System.out.println(i.decrementAndGet());
//获取并自减(i=1,结果i=0,返回1),类似i--
System.out.println(i.getAndDecrement());
//获取并加值(i=0,结果i=5,返回0)
System.out.println(i.getAndAdd(5));
//加值并获取(i=5,结果i=0,返回0)
System.out.println(i.addAndGet(5));
//更新并获取(i=5,结果i=10,返回50)
System.out.println(i.updateAndGet(value->value*10));
/*
updateAndGet方法中传入的是函数式接口,可以直接表达式书写,
上例中的value->value*10,value是读取值,value*10是设置值
*/
//获取并更新(i=5,结果i=50,返回5)
System.out.println(i.getAndUpdate(value->value*10));
注意以上方法内部均用到原生的CAS实现,所以例子中取钱的实现可以直接改为:
public void withdraw(Integer amout) {
/*
while(true){
int prev=balance.get();//获取balance的最新值
//要修改后的余额
int next=prev-amout;
//真正修改
if(balance.compareAndSet(prev,next)){
break;
}//修改前和修改后的值传入
}
*/
balance.getAndAdd(-1*amount);
}
}
getAndAdd源码:
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
updateAndGet源码:
public final int updateAndGet(IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get();
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev, next));
return next;
}
@FunctionalInterface
public interface IntUnaryOperator {
/**
* Applies this operator to the given operand.
*
* @param operand the operand
* @return the operator result
*/
int applyAsInt(int operand);
/**
* Returns a composed operator that first applies the {@code before}
* operator to its input, and then applies this operator to the result.
* If evaluation of either operator throws an exception, it is relayed to
* the caller of the composed operator.
*
* @param before the operator to apply before this operator is applied
* @return a composed operator that first applies the {@code before}
* operator and then applies this operator
* @throws NullPointerException if before is null
*
* @see #andThen(IntUnaryOperator)
*/
default IntUnaryOperator compose(IntUnaryOperator before) {
Objects.requireNonNull(before);
return (int v) -> applyAsInt(before.applyAsInt(v));
}
/**
* Returns a composed operator that first applies this operator to
* its input, and then applies the {@code after} operator to the result.
* If evaluation of either operator throws an exception, it is relayed to
* the caller of the composed operator.
*
* @param after the operator to apply after this operator is applied
* @return a composed operator that first applies this operator and then
* applies the {@code after} operator
* @throws NullPointerException if after is null
*
* @see #compose(IntUnaryOperator)
*/
default IntUnaryOperator andThen(IntUnaryOperator after) {
Objects.requireNonNull(after);
return (int t) -> after.applyAsInt(applyAsInt(t));
}
/**
* Returns a unary operator that always returns its input argument.
*
* @return a unary operator that always returns its input argument
*/
static IntUnaryOperator identity() {
return t -> t;
}
}
updateAndGet方法传入的参数是 IntUnaryOperator 类变量,即将一个“操作”作为参数传入了方法,IntUnaryOperator是一个函数式接口,其中只有一个抽象方法等待实现:applyAsInt(int operand),传入一个int数,具体操作在实现类中定义。
方法的使用例子:
//更新并获取(i=5,结果i=10,返回50)
System.out.println(i.updateAndGet(value->value*10));
二、原子引用
共享变量的类型不只有基本类型,还有引用类型,即各个类的对象,所以原子引用的作用是:使得原引用类型的共享变量变为原子引用类型的共享变量,使得对该引用对象的操作都是原子操作,保证线程安全性。
原子引用与原子变量的用法一致,只不过因为是自定义的引用,对共享变量的访问操作需要使用原生CAS自定义。
- AtomicReference
- AtomicMarkableReference
- AtomicStampedReference
注意:
引用类型的共享变量,如果更改的话是更改引用本身,相当于改变指针,而没有改变引用所指对象里的内容。
AtomicReference
银行取钱例子改进,共享变量银行余额从Integer变成Decimal,因为没有AtomicDecimal类,所以需要使用AtomicReference将Decimal类对象保护起来,注意AtomicReference类是泛型类,需要指定保护共享变量的原类型,用法如下例子中的:
private AtomicReference
import java.math.BigDecimal;
import java.sql.SQLOutput;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
public class TestAccount {
public static void main(String[] args) {
DecimalAccount.demo(new DecimalAccountCas(new BigDecimal("10000")));
}
}
class DecimalAccountCas implements DecimalAccount{
//是引用,修饰成原子引用
private AtomicReference<BigDecimal> balance;
public DecimalAccountCas(BigDecimal balance){
this.balance=new AtomicReference<>(balance);
}
@Override
public BigDecimal getBalance() {
return balance.get();
}
@Override
public void withdraw(BigDecimal amount) {
while(true){
//获取旧值
BigDecimal prev = balance.get();
BigDecimal next = prev.subtract(amount);
if(balance.compareAndSet(prev,next)){
break;
}
}
}
}
interface DecimalAccount{
//获取余额
BigDecimal getBalance();
//取款
void withdraw(BigDecimal amout);
/*
* 方法内会启动1000个线程,每个线程作 -10元 的操作
* 如果初始余额为 10000 那么线程正确执行的结果应为0元
* */
/*
*jdk1.8之后支持接口中定义并实现静态方法
* 本方法即1000个线程去一个银行对象取钱,1000个线程去调用getBalance方法与withdraw方法
* */
static void demo(DecimalAccount account){
List<Thread> ts = new ArrayList<>();//创建线程集合
for (int i=0;i<1000;i++){
ts.add(new Thread(()->{
account.withdraw(BigDecimal.TEN);
}));
}
long start = System.nanoTime();
ts.forEach(Thread::start);//每个线程执行start方法
ts.forEach(t->{
try{
t.join();
}catch (InterruptedException e){
e.printStackTrace();
}
});
long end= System.nanoTime();
System.out.println(account.getBalance()+" cost:"+(end-start)/1000_000+"ms");
}
}
AtomicStampedReference
ABA问题
AtomicReferece引用保护的共享变量,无法察觉到最新值是否改变过,其compareAndSet方法仅仅是根据prev值与获取的最新值是否相等来判断CAS操作是否成功。比如共享变量ref原值为A,线程t1与线程t2将其改为A->B,B->A,那么对于主线程来讲,prev值为A,刚获取的ref的最新值也是A,CAS成功。但实际上值已经发生过改变。
如果希望只要有其它线程【动过了】共享变量,那么自己的cas就算失败,这时,仅比较值是不够的,需要再加一个版本号,引入AtomicStampedReference引用。
compareAndSet操作除了原先的期望值+修改值两个参数,还加入期望版本值与更改的版本值两个参数。
- 如果stamp最新值与stamp预期值不同,代表预期值已经被修改过,那么cas直接失败;
- 如果stamp最新值与stamp预期值相同,再去比较prev与内存中的最新值是否相同以判断cas能够成功; ```java import java.util.concurrent.atomic.AtomicStampedReference;
public class Test{
//初始stamp值为0
static AtomicStampedReference<String> ref= new AtomicStampedReference<>("A",0);
public static void main(String[] args) throws InterruptedException {
System.out.println("main start...");
String prev= ref.getReference();//获取值
//获取版本号
int stamp = ref.getStamp();
System.out.println("版本号:"+stamp);
other();
Thread.sleep(1000);
System.out.println(ref.compareAndSet(prev,"C",stamp,stamp+1));
}
public static void other(){
new Thread(()->{
int stamp = ref.getStamp();//得到时间戳
System.out.println("版本号:"+stamp);
System.out.println(ref.compareAndSet(ref.getReference(),"B",stamp,stamp+1));
},"t1").start();
new Thread(()->{
int stamp = ref.getStamp();//得到时间戳
System.out.println("版本号:"+stamp);
System.out.println(ref.compareAndSet(ref.getReference(),"A",stamp,stamp+1));
},"t2").start();
}
}
<a name="INRbh"></a>
### AtomicMarkableReference
实际上一些应用场景并不关心值被更改了几次,而是关心是否被更改,那么即可以使用AtomicMarkableReference类
与AtomicStampedReference类似,在创建对象的时候直接指定标志位
```java
//初始标志位的值为true值
static AtomicMarkableReference<String> ref = new AtomicMarkableReference<>("A",true);
- 使用的时候如果值与设定的标志值相同,比如设定值为true,预期值也为true,那么cas操作成功;
- 如果预期值与设定值不同,那么cas失败;