对象属性进行原子操作
- 获取属性偏移量
- 通过 CAS 方式进行修改
public class UnsafeTest { public static Unsafe U; static { try { Field f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); U = (Unsafe)f.get(null); } catch (Throwable e) { e.printStackTrace(); } } public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException { User user = new User(); // 获取字段 Field age = user.getClass().getDeclaredField("age"); // 获取字段相对Java对象的"起始地址"的偏移量 long offset = U.objectFieldOffset(age); // 设置值 boolean success = U.compareAndSwapInt(user, offset, 10, 20) ; System.out.println("修改结果: " + success ) ; // 打印数据 System.out.println("查询结果: " + user.getAge()); } } class User { private int age; public User() { this.age = 10; } public int getAge() { return age; } } //输出结果 修改结果: true 查询结果: 20
注意事项
ABA 问题
ABA 是在 CAS 场景下面容易出现的问题,当一个数据的值为 A1, 然后再被修改为 B ,最后再修改为 A2。 并且 A1 和 A2 相等;但是另外一个线程查询到的结果是 A1 , 此时更新为 C ,也可以成功。这样的风险就是后者不清楚数据是否被改变,并且能够成功修改。
举个例子:你钱包里面有 100 块钱,然后小偷偷走后又给你换回来了,你看到的还是钱包里面的 100 块钱吗?再比如你老婆和其他的男人出轨了,然后再还回来,你看到的还是你的老婆吗?其实这些问题已经触犯到道德和FL的范围。
针对 ABA 问题,我们可以通过 AtomicStampedReference
解决。
AtomicStampedReference
AtomicStampedReference 维护一个对象引用以及一个整数“stamp”,该整数可以进行原子更新。 实现说明:此实现通过创建表示“装箱”[reference,integer] 对的内部对象来维护戳记引用。在此之前我们先对 ABA 问题进行一个模拟:
public class ABATest { private static AtomicInteger index = new AtomicInteger(10); public static void main(String[] args) { new Thread(()-> { index.compareAndSet(10, 101); index.compareAndSet(101, 10); System.out.println(Thread.currentThread() + ": 10->101->10"); }, "Tom").start(); new Thread(()-> { try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } boolean result = index.compareAndSet(10, 1000); System.out.println(Thread.currentThread() + ": 更新结果:" + result + ", 更新后的值: " + index.get()); }, "Jack").start(); } }
通过 AtomicStampedReference 来解决这个问题:就是我们在进入第二个线程之前,先读取当前的版本号,然后进入更新。这个读取的版本号可能是一个旧的值。如果出现这种情况,那么我们执行 compareAndSet
就会返回失败。
public class ABATest2 { private static AtomicStampedReference index = new AtomicStampedReference(10, 1); public static void main(String[] args) { new Thread(() -> { index.compareAndSet(10, 101, index.getStamp(), index.getStamp() + 1); index.compareAndSet(101, 10, index.getStamp(), index.getStamp() + 1); System.out.println(Thread.currentThread() + ": 10->101->10"); }, "Tom").start(); int stamp = index.getStamp(); new Thread(() -> { try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } boolean result = index.compareAndSet(10, 1000, stamp, stamp + 1); System.out.println(Thread.currentThread() + ": 更新结果:" + result + ", 更新后的值: " + index.getReference()); }, "Jack").start(); } }
返回结果如下所示:
Thread[Tom,5,main]: 10->101->10 Thread[Jack,5,main]: 更新结果:false, 更新后的值: 10