第一章讲解了volatile不保证原子性,为解决原子性使用了AtomicInteger原子整型,解决了基本类型运算操作的原子性的问题,那我们自定义的实体类或者基本数据类型都要保证原子性呢?使用AtomicReference原子引用
AtomicInteger原子整型:
package com.javaliao.backstage; import java.util.concurrent.atomic.AtomicInteger; class MyData{ volatile int number = 0; AtomicInteger atomicInteger = new AtomicInteger(); public void changeData(){ atomicInteger.getAndIncrement();//加一 } } /** * 线程对变量的读取赋值要先将变量从主内存拷贝自己的工作内存空间,在工作内存中进行操作,操作完成后再将变量写回主内存 */ public class Demo { //主线程main,程序入口 public static void main(String[] args) { //创建对象,number在主内存为0 MyData myData = new MyData(); for (int i = 1; i <= 20; i++) { //创建20个线程 new Thread(()->{ //一个线程执行1000次加一的操作 for (int j = 1; j <= 1000; j++) { myData.changeData(); } },String.valueOf(i)).start(); } //程序不关闭会继续执行main线程和GC线程,判断线程数量大于二继续执行上面的代码, while (Thread.activeCount() > 2){ Thread.yield(); } //理想中number的数量为20*1000=20000,而volatile不保证原子性,实际情况一般打印number的数量不是20000 System.out.println(Thread.currentThread().getName()+"\t 打印number的数量:" + myData.atomicInteger); } }
AtomicReference原子引用直接上代码:
package com.javaliao.backstage; import java.util.concurrent.atomic.AtomicReference; class User{ String userName; int age; public User(String userName, int age) { this.userName = userName; this.age = age; } @Override public String toString() { return "User{" + "userName='" + userName + '\'' + ", age=" + age + '}'; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } public class Demo { //主线程main,程序入口 public static void main(String[] args) { User user1 = new User("java_wxid",25); User user2 = new User("javaliao",22); AtomicReference<User> atomicReference = new AtomicReference<>(); atomicReference.set(user1); System.out.println(atomicReference.compareAndSet(user1, user2)+"\t"+atomicReference.get().toString()); new Thread(()->{ System.out.println(atomicReference.compareAndSet(user1, user1)+"\t"+atomicReference.get().toString()); },"a").start(); } }
控制台:
但是这不能解决上一章讲解的CAS的ABA问题
ABA问题代码:
public class Demo { static AtomicReference<Integer> atomicReference = new AtomicReference<>(100); public static void main(String[] args) { new Thread(()->{ //执行ABA操作 atomicReference.compareAndSet(100,101); atomicReference.compareAndSet(101,100); },"t1").start(); new Thread(()->{ try { //暂停一秒,保证t1线程完成了一次ABA操作 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(atomicReference.compareAndSet(100, 2019)); System.out.println(atomicReference.get()); },"t2").start(); } }
上一章讲了这中间有猫腻,所以提供解决方案:
使用AtomicStampedReference版本号原子引用
只要T1的版本号弱于T2的线程版本号就需要更新,假设线程T1的第二个版本号的值为2019,而线程T2已经修改了二次了,版本号为3,那此时就不能那线程T2的版本号为2的进行比较并交换,需要重新将线程T3的版本号的值拷贝更新再进行操作。
package com.javaliao.backstage; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicStampedReference; public class Demo { static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,1); public static void main(String[] args) { System.out.println("===============解决ABA问题方案==============="); new Thread(()->{ //获取版本号 int stamp = atomicStampedReference.getStamp(); System.out.println(Thread.currentThread().getName()+"第一次版本号:"+stamp+"\t 当前实际最新值:"+atomicStampedReference.getReference()); try { //暂停一秒 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1); System.out.println(Thread.currentThread().getName()+"\t 第二次版本号:"+atomicStampedReference.getStamp()+"\t 当前实际最新值:"+atomicStampedReference.getReference()); atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1); System.out.println(Thread.currentThread().getName()+"\t 第三次版本号:"+atomicStampedReference.getStamp()+"\t 当前实际最新值:"+atomicStampedReference.getReference()); },"t3").start(); new Thread(()->{ //获取版本号 int stamp = atomicStampedReference.getStamp(); System.out.println(Thread.currentThread().getName()+"\t 第一次版本号:"+stamp); try { //暂停一秒,保证t3线程完成了一次ABA操作 Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"\t 最新版本号:"+atomicStampedReference.getStamp()+"\t 当前t4的版本号是:"+stamp); System.out.println(Thread.currentThread().getName()+"\t 只有最新的版本号和t4的版本号一致时,才可以写回主内存,是否写回成功:"+ atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1)); System.out.println(Thread.currentThread().getName()+"\t 当前实际最新值:"+atomicStampedReference.getReference()); },"t4").start(); } }
控制台:
这个时候就可以让t4线程去更新版本号为3的值100,解决了CAS只管结果不管过程的问题。