说Atomic类之前,先聊一聊volatile。

对volatile的第一印象就是可见性。所谓可见性,就是一个线程对共享变量的修改,别的线程能够感知到。

但是对于原子性,volatile是不能保证的。来看看自增操作的问题:

  1. volatile int i;
  2.  
  3. i++;

i++ 在多线程环境下,是不能保证最终的结果正确的。比如某个时刻,i=5,线程A读取了i的值,说时迟那时快,就在马上要执行++操作时,线程A突然就被切换走了;然后线程B也读取i的值,进行了++操作。这时i的值是6,即使线程A的工作内存中的缓存已经失效,线程A已经读取了i的值为5,不会再去读取,所以++操作后,i的值还是6。

关于volatile的底层实现,有好多文章分析的很透彻,这里不再赘述。

那么除了使用synchronized,还有没有其它的方式来解决上述问题呢?天空一声巨响,Atomic类闪亮登场!!!

先看看AtomicInteger类,其它类型包装成的Atomic类,请读者自习。

  1. AtomicInteger i = new AtomicInteger(0);
  2.  
  3. i.incrementAndGet();

用法相当简单,incrementAndGet 方法能实现原子性的自增操作。如何实现,看源码。

  1. /**
  2. * Atomically increments by one the current value.
  3. *
  4. * @return the updated value
  5. */
  6. public final int incrementAndGet() {
  7. return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
  8. }

Unsafe类:

  1. /**
  2. * Atomically adds the given value to the current value of a field
  3. * or array element within the given object <code>o</code>
  4. * at the given <code>offset</code>.
  5. *
  6. * @param o object/array to update the field/element in
  7. * @param offset field/element offset
  8. * @param delta the value to add
  9. * @return the previous value
  10. * @since 1.8
  11. */
  12. public final int getAndAddInt(Object o, long offset, int delta) {
  13. int v;
  14. do {
  15. v = getIntVolatile(o, offset);
  16. } while (!compareAndSwapInt(o, offset, v, v + delta));
  17. return v;
  18. }
  1. /**
  2. * Atomically update Java variable to <tt>x</tt> if it is currently
  3. * holding <tt>expected</tt>.
  4. * @return <tt>true</tt> if successful
  5. */
  6. public final native boolean compareAndSwapInt(Object o, long offset,
  7. int expected,
  8. int x);

可以看到原子性的实现没有用synchronized,说明是非阻塞同步。最核心的方法是compareAndSwapInt,也就是所谓的CAS操作。

CAS操作依赖底层硬件的CAS指令,CAS指令有两个步骤:冲突检测和更新操作,但是这两个步骤合起来成为一个原子性操作。

CAS指令需要3个操作数:内存位置(V),旧的预期值(A),新值(B)。CAS指令执行时,首先比较内存位置V处的值和A的值是否相等(冲突检测),如果相等,就用新值B覆盖A(更新操作),否则,就什么也不做。所以,一般循环执行CAS操作,直到成功为止。

Unsafe类里面的compareAndSwapXXX 方法最后都会变成与硬件相关的CAS指令。从Unsafe这个类名就可以看出,作者不希望我们随便使用,因为是不安全的。为什么不安全呢,因为这个类可以直接操作内存;还有其他的一些底层操作,比如上篇文章提到的将线程挂起,就是调用了Unsafe类的park方法(感兴趣的,出门左拐)。

了解了CAS和Unsafe类,接着再说AtomicInteger类:

  1. // setup to use Unsafe.compareAndSwapInt for updates
  2. private static final Unsafe unsafe = Unsafe.getUnsafe();
  3. private static final long valueOffset;
  4.  
  5. static {
  6. try {
  7. valueOffset = unsafe.objectFieldOffset
  8. (AtomicInteger.class.getDeclaredField("value"));
  9. } catch (Exception ex) { throw new Error(ex); }
  10. }
  11.  
  12. private volatile int value;

value就是我们需要操作的真正int值,unsafe就是Unsafe类的单例,valueOffset在static语句块里面,被设置成了value变量在AtomicInteger类的实例对象里面的偏移量(可以看成内存地址)。这里对对象的内存布局如有疑问,同样出门一路左拐,找到那篇将Oop和Klass的文章。

也就是说,Atomic类通过循环进行CAS操作,直到成功,来实现非阻塞同步,进而变成原子操作。

在java.util.concurrent.atomic包下还有一些看上去比较奇怪的类,XXXFieldUpdater类,这玩意儿是用来干什么的呢?

一句话概括,就是亡羊补牢。比如说,你先自己写了一个类,定义了一个基础类型的变量。后来涉及到多线程,那么原来对该变量的一些操作就变得不安全。如果,你立马想到的是手动修改代码,那就太low了,破坏了设计模式里面比较重要的开闭原则,而且很多情况下,你是接触不到别人的源代码的。这个时候,XXXFieldUpdater类就派上用场了。这个时候,猜也能猜到,这个类肯定是通过反射的方式实现这个功能的。另外,有一点原来定义的继承类型变量,必须是volatile的。

直接上代码,看效果:

  1. public class Test{
  2. public volatile int a = 100;
  3.  
  4. // 多线程下不安全
  5. public void incr() {
  6. a++;
  7. }
  8. }
  1. public class SafeTest{
  2. private static AtomicIntegerFieldUpdater<Test> update = AtomicIntegerFieldUpdater.newUpdater(Test.class, "a");
  3. private static Test test = new Test();
  4.  
  5. // 多线程下安全
  6. public void incr() {
  7. update.incrementAndGet(test);
  8. }
  9. }
  1. update.incrementAndGet(test)无非也是调用了Unsafe类的CAS操作,核心方法是AtomicIntegerFieldUpdater.newUpdater(Test.class, "a"),看看源码:
  1. @CallerSensitive
  2. public static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass,
  3. String fieldName) {
  4. return new AtomicIntegerFieldUpdaterImpl<U>
  5. (tclass, fieldName, Reflection.getCallerClass());
  6. }
  7.  
  8. AtomicIntegerFieldUpdaterImpl(final Class<T> tclass,
  9. final String fieldName,
  10. final Class<?> caller) {
  11. // 反射逻辑
  12. final Field field;
  13. final int modifiers;
  14. try {
  15. field = AccessController.doPrivileged(
  16. new PrivilegedExceptionAction<Field>() {
  17. public Field run() throws NoSuchFieldException {
  18. return tclass.getDeclaredField(fieldName);
  19. }
  20. });
  21. modifiers = field.getModifiers();
  22. sun.reflect.misc.ReflectUtil.ensureMemberAccess(
  23. caller, tclass, null, modifiers);
  24. ClassLoader cl = tclass.getClassLoader();
  25. ClassLoader ccl = caller.getClassLoader();
  26. if ((ccl != null) && (ccl != cl) &&
  27. ((cl == null) || !isAncestor(cl, ccl))) {
  28. sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass);
  29. }
  30. } catch (PrivilegedActionException pae) {
  31. throw new RuntimeException(pae.getException());
  32. } catch (Exception ex) {
  33. throw new RuntimeException(ex);
  34. }
  35.  
  36. Class<?> fieldt = field.getType();
  37. if (fieldt != int.class)
  38. throw new IllegalArgumentException("Must be integer type");
  39. // 必须是volatile
  40. if (!Modifier.isVolatile(modifiers))
  41. throw new IllegalArgumentException("Must be volatile type");
  42.  
  43. this.cclass = (Modifier.isProtected(modifiers) &&
  44. caller != tclass) ? caller : null;
  45. this.tclass = tclass;
  46. // 得到变量的偏移量,相当于内存地址
  47. offset = unsafe.objectFieldOffset(field);
  48. }

总的来说,原子性的实现是依赖于Unsafe类的CAS操作,直接修改内存里的值,既危险又刺激。我们甚至可以用Unsafe类来直接分配内存,要不试一试!!!

Unsafe类是单例,可以通过它的getUnsafe方法获取这个单例。

  1. @CallerSensitive
  2. public static Unsafe getUnsafe() {
  3. Class<?> caller = Reflection.getCallerClass();
  4. // 调用方的类的类加载器必须是启动类加载器
  5. if (!VM.isSystemDomainLoader(caller.getClassLoader()))
  6. throw new SecurityException("Unsafe");
  7. return theUnsafe;
  8. }

这个方法对调用方有限制,就是说你随随便便定义的类,调用这个方法是会报错的。但是Unsafe类既然已经被加载,我们可以通过反射的方式去获取里面的单例对象。

  1. package test;
  2.  
  3. import java.lang.reflect.Field;
  4.  
  5. import sun.misc.Unsafe;
  6. import sun.reflect.Reflection;
  7.  
  8. class User {
  9. private String name = "";
  10. private int age = 0;
  11.  
  12. public User() {
  13. this.name = "test";
  14. this.age = 25;
  15. }
  16.  
  17. @Override
  18. public String toString() {
  19. return name + ": " + age;
  20. }
  21. }
  22.  
  23. public class Test {
  24. public static void main(String[] args) throws NoSuchFieldException,
  25. SecurityException, IllegalArgumentException, IllegalAccessException, InstantiationException {
  26. // 通过反射得到theUnsafe对应的Field对象
  27. Field field = Unsafe.class.getDeclaredField("theUnsafe");
  28. // 设置该Field为可访问
  29. field.setAccessible(true);
  30. // 通过Field得到该Field对应的具体对象,传入null是因为该Field为static的
  31. Unsafe unsafe = (Unsafe) field.get(null);
  32.  
  33. // 直接分配相应大小的内存,不执行构造方法
  34. User user = (User) unsafe.allocateInstance(User.class);
  35. System.out.println(user);
  36.  
  37. User userFromNormal = new User();
  38. System.out.println(userFromNormal);
  39.  
  40. }
  41. }

这个例子演示了Unsafe直接为某个类的实例分配内存,注意是只分配内存,没有顺便调用构造方法。

运行结果:

  1. null: 0
  2. test: 25

当然,Unsafe类里面还有好多的底层操作,宝藏后面慢慢挖掘。就到这里吧!!!

  1.  

Atomic类和CAS的更多相关文章

  1. 推荐使用concurrent包中的Atomic类

        这是一个真实案例,曾经惹出硕大风波,故事的起因却很简单,就是需要实现一个简单的计数器,每次取值然后加1,于是就有了下面这段代码:           private int counter = ...

  2. Java中Atomic类的使用分析

    1:为什么会出现Atomic类 在多线程或者并发环境中,我们常常会遇到这种情况 int i=0; i++ 稍有经验的同学都知道这种写法是线程不安全的.为了达到线程安全的目的,我们通常会用synchro ...

  3. 随意看看AtomicInteger类和CAS

    最近在读jdk源码,怎么说呢?感觉收获还行,比看框架源码舒服多了,一些以前就感觉很模糊的概念和一些类的用法也清楚了好多,举个很简单的例子,我在读Integer类的时候,发现了原来这个类自带缓存,看看如 ...

  4. java中的Atomic类

    文章目录 问题背景 Lock 使用Atomic java中的Atomic类 问题背景 在多线程环境中,我们最常遇到的问题就是变量的值进行同步.因为变量需要在多线程中进行共享,所以我们必须需要采用一定的 ...

  5. 并发编程从零开始(十一)-Atomic类

    并发编程从零开始(十一)-Atomic类 7 Atomic类 7.1 AtomicInteger和AtomicLong 如下面代码所示,对于一个整数的加减操作,要保证线程安全,需要加锁,也就是加syn ...

  6. Java多线程系列九——Atomic类

    参考资料:https://fangjian0423.github.io/2016/03/16/java-AtomicInteger-analysis/http://www.cnblogs.com/54 ...

  7. java.util.concurrent.atomic 类包详解

    java.util.concurrent包分成了三个部分,分别是java.util.concurrent.java.util.concurrent.atomic和java.util.concurren ...

  8. CAS机制总结

    一.简介 CAS机制:(Compare and set)比较和替换 简单来说–>使用一个期望值来和当前变量的值进行比较,如果当前的变量值与我们期望的值相等,就用一个新的值来更新当前变量的值CAS ...

  9. 无锁同步-JAVA之Volatile、Atomic和CAS

    1.概要 本文是无锁同步系列文章的第二篇,主要探讨JAVA中的原子操作,以及如何进行无锁同步. 关于JAVA中的原子操作,我们很容易想到的是Volatile变量.java.util.concurren ...

随机推荐

  1. (转)Spring boot——logback.xml 配置详解(二)

    文章转载自:http://aub.iteye.com/blog/1101260,在此对作者的辛苦表示感谢! 1 根节点<configuration>包含的属性 scan: 当此属性设置为t ...

  2. (转)SimpleDateFormat使用

    1  SimpleDateFormat public class SimpleDateFormat extends DateFormat SimpleDateFormat 是一个以国别敏感的方式格式化 ...

  3. 一.CPU,Mem过高怎么办 --这是个开始

    本身是名Java开发,在做了一段大数据的工作后,猛然间想对Java做个总结. 从未写过技术博客,一时不知如何开始,思虑后,暂且以自己喜爱的方式来开篇. 工作中遇到过CPU或内存过高的问题,解决步骤: ...

  4. [BZOJ 3319] 黑白树

    3319: 黑白树 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 557  Solved: 194[Submit][Status][Discuss] ...

  5. Python3常用网络编程模块介绍

    一.socket模块 网络服务都是建立在socket基础之上的,socket是网络连接端点,是网络的基础:每个socket都被绑定到指定的IP和端口上: 1.首先使用socket(family=AF_ ...

  6. suffix tree

    文章出处:http://www.cnblogs.com/snowberg/archive/2011/10/21/2468588.html   3   What is a Suffix Tree Suf ...

  7. Linux 粘着位(sticky bit)

    当设置粘着位时只有root或者owner才能删除.重命名文件. 示例: 用户apple默认组为fruit. [root@titan ~]# id apple uid=1001(apple) gid=1 ...

  8. Xmemcached学习笔记

    memcached有三种java客户端 第一种:Com.danga 包下面的memcached,需引入jar(本人用的是memcached-2.5.2.jar 文末附上附件需要的可以下载) 第二种:s ...

  9. 修改maven的默认JDK

    在我们实际使用IDEA开发maven项目的时候,创建maven项目的默认版本是jdk1.5,当然我们可以通过其他手段去修改module的JDK版本,或JDK的编译级别等等,但是如果每次你都这样修改,那 ...

  10. Oracle安装oraInventory问题

    Oracle安装oraInventory问题-----------------------------2013/10/15 在使用安装Oracle软件或者使用dbca创建数据库时,所有的日志都会放在o ...