一、原子整数
J.U.C 并发包提供了:
- AtomicBoolean
- AtomicInteger
- AtomicLong
这几个类保证对包装的值的操作是原子操作,故线程安全。
以 AtomicInteger 为例
//获取并自增(i=0,结果i=1,返回0),类似i++System.out.println(i.getAndIncrement());//自增并获取(i=1,结果i=2,返回2),类似++iSystem.out.println(i.incrementAndGet());//自减并获取(i=2,结果i=1,返回1),类似--iSystem.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;}@FunctionalInterfacepublic 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);}@Overridepublic BigDecimal getBalance() {return balance.get();}@Overridepublic 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值为0static 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失败;
