https://segmentfault.com/blog/ressmix_multithread?page=2 Volatile Actomic CAS ThreadLocal Unsafe类 同步类容器 并发类容器 并发无阻塞式队列 并发阻塞式队列

Volatile关键字

volatile关键字的作用式保证变量在多线程之间的可见性,如果一个字段被声明为volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的。与Synchronized不同。volatile变量不会引起线程上下文的切换和调度,在适合场景下拥有更低的执行成本和更高的执行效率。

volatile实现原理有两点:

  • 修改volatile变量时会强制将修改后的值刷新的主内存中。
  • 修改volatile变量后导致其他线程工作内存中对应的变量值失效。因此,再读取该变量值的时候就需要重新读取主内存中的值。

    Unsafe类

    image.png

    Unsafe类,来源于 sun.misc包。该类封装了许多类似指针的操作,可以直接进行内存管理、操作对象、阻塞/唤醒线程等操作。Java本身不需要指针的操作,所以这也是该类命名为unsafe的原因之一

JUC中许多CAS方法底层实现都是基于Unsafe类在操作。
比如AtomicBoolean的compareAndSet方法:

unsafe.compareAndSwapInt方法是个native方法。(如果对象中的字段值与期望值相等,则将字段值修改为x,然后返回true;否则返回false):
Unsafe类中CAS方法都是native方法,需要通过CAS原子指令完成。在讲AQS时,里面有许多涉及CLH队列的操作,其实就是通过Unsafe类完成的指针操作。

Unsafe对象的创建

Unsafe是一个final类,不能被继承,也没有公共的构造器,只能通过工厂方法getUnsafe获得Unsafe的单例。

但是getUnsafe方法限制了调用该方法的类的类加载器必须为Bootstrap ClassLoader

  1. @CallerSensitive
  2. public static Unsafe getUnsafe() {
  3. Class<?> caller = Reflection.getCallerClass();
  4. if (!VM.isSystemDomainLoader(caller.getClassLoader()))
  5. throw new SecurityException("Unsafe");
  6. return theUnsafe;
  7. }
类加载器名称 作用
Bootstrap类加载器(Bootstrap ClassLoader) 主要加载的是JVM自身需要的类,这个类加载使用C++语言实现的,是JVM自身的一部分,它负责将 【JDK的安装目录】/lib路径下的核心类库,如rt.jar
扩展类加载器(Extension ClassLoader) 该加载器负责加载【JDK的安装目录】jrelibext目录中的类库,开发者可以直接使用该加载器
系统类加载器(Application ClassLoader) 负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,也是默认的类加载器

所以在用户代码中直接调用getUnsafe方法,会抛出异常。因为用户自定义的类一般都是由系统类加载器加载的。

  1. /**
  2. * Unsafe类的安全限制
  3. */
  4. public class UnsafeDemo0 {
  5. private int age;
  6. public int getAge() {
  7. return age;
  8. }
  9. public static void main(String[] args) {
  10. UnsafeDemo0 demo0 = new UnsafeDemo0();
  11. // 获取Unsafe类实例
  12. Unsafe unsafe = Unsafe.getUnsafe();
  13. try {
  14. //获取age属性的内存偏移地址
  15. long ageOffset = unsafe.objectFieldOffset(UnsafeDemo0.class.getDeclaredField("age"));
  16. //设置age的值为11
  17. unsafe.putInt(demo0,ageOffset,11);
  18. //输出结果
  19. System.out.println(demo0.getAge());
  20. } catch (NoSuchFieldException e) {
  21. e.printStackTrace();
  22. }
  23. }
  24. }
  1. //运行结果
  2. Exception in thread "main" java.lang.SecurityException: Unsafe
  3. at jdk.unsupported/sun.misc.Unsafe.getUnsafe(Unsafe.java:97)
  4. at com.mkevin.demo3.UnsafeDemo0.main(UnsafeDemo0.java:20)

但是,是否就真的没有办法获取到Unsafe实例了呢?当然不是,要获取Unsafe对象的方法很多,这里给出一种通过反射的方法:

  1. /**
  2. * 突破Unsafe类的安全限制
  3. */
  4. public class UnsafeDemo1 {
  5. private int age;
  6. public int getAge() {
  7. return age;
  8. }
  9. public static void main(String[] args) {
  10. UnsafeDemo1 demo0 = new UnsafeDemo1();
  11. try {
  12. //通过反射获取Unsafe的静态成员变量theUnsafe
  13. Field field = Unsafe.class.getDeclaredField("theUnsafe");
  14. //设置此字段为可存取
  15. field.setAccessible(true);
  16. //获取变量的值,强转为Unsafe类对象
  17. Unsafe unsafe = (Unsafe) field.get(null);
  18. //获取age属性的内存偏移地址
  19. long ageOffset = unsafe.objectFieldOffset(UnsafeDemo1.class.getDeclaredField("age"));
  20. //设置age的值为11
  21. unsafe.putInt(demo0,ageOffset,11);
  22. System.out.println("KEVIN-1>>"+demo0.getAge());
  23. //获取age
  24. int age = unsafe.getInt(demo0,ageOffset);
  25. System.out.println("KEVIN-1-getInt>>"+age);
  26. //验证CAS方法
  27. boolean result = unsafe.compareAndSwapInt(demo0,ageOffset,10,50);
  28. System.out.println("KEVIN-2>>"+demo0.getAge()+","+result);
  29. //验证CAS方法
  30. result = unsafe.compareAndSwapInt(demo0,ageOffset,11,100);
  31. System.out.println("KEVIN-3>>"+demo0.getAge()+","+result);
  32. //获取并设置
  33. age = unsafe.getAndSetInt(demo0,ageOffset,99);
  34. System.out.println("KEVIN-4-getAndSetInt>>"+age+","+demo0.getAge());
  35. //获取并增加
  36. age = unsafe.getAndAddInt(demo0,ageOffset,100);
  37. System.out.println("KEVIN-5-getAndAddInt>>"+age+","+demo0.getAge());
  38. } catch (NoSuchFieldException | IllegalAccessException e) {
  39. e.printStackTrace();
  40. }
  41. }
  42. }

但是,除非对Unsafe的实现非常清楚,否则应尽量避免直接使用Unsafe来进行操作。

同步类容器

即线程安全类容器,由synchronized修饰的集合

  • Vector、HashTable等古老的并发容器,都是使用Collections.synchronizedXXX等工厂方法创建的,并发状态下只能有一个线程访问容器对象,性能很低。
  • 原理是在整个容器上加一把锁(synchronized),保证每次只能由一个线程访问该对象。

并发类容器

ConcurrentMap

  • ConcurrentHashMap替代HashMap、HashTable
  • ConcurrentSkipListMap替代TreeMap
  • 底层原理:不是使用synchronized,ConcurrentHashMap将hash表分为16个segment,每个segment单独进行所控制,从而减小了锁的粒度(相当于最高支持16个线程同时访问hash表),提升了性能。

    COW类并发容器

    • copy on write容器,简称COW;写时复制容器,想容器中添加元素时,先将容器进行Copy出一个新的容器,然后将元素添加到新容器中,再将原容器的引用指向新容器。并发读的时候不需要锁定容器,因为原容器没有变化,使用的是一种读写分离的思想。由于每次更新都会复制新容器,所以如果数据量较大,并且更新操作频繁则对内存消耗很高,建议在高并发读的场景下使用。
    • CopyOnWriteArraySet基于CopyOnWriteArrayList实现,其唯一的不同是在add时调用的是CopyOnWriteArrayList的addIfAbsebt方法,adIfAbsent方法同样采用锁保护,并创建一个新的大小+1的Object数组。遍历当前Object数组,如果Object数组已经有了当前元素,则直接返回,如果没有则放入Object数组的尾部,并返回。从以上分析可见,CopyOnWriteArraySet在add时每次都要进行数组遍历,因此其性能会低于CopyOnWriteArrayList。

COW迭代器的弱一致性问题

  • 使用COW容器的iterator方法实际返回的是COWIterator实例,遍历数据为快照数据,其他线程对于容器元素增加、删除、修改不对快照产生影响。
  • 对java.concurrent.CopyOnWriteArrayList、Java.util.comcurrent.CopyOnWriteArraySet均适用。

    1. /**
    2. * CopyOnWriteArrayList 的迭代器也为弱一致性迭代器
    3. */
    4. public class COWDemo0 {
    5. public static void main(String[] args) throws InterruptedException {
    6. CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
    7. list.add(1);
    8. list.add(2);
    9. list.add(3);
    10. Iterator<Integer> iterator = list.iterator();
    11. Thread td = new Thread(new Runnable() {
    12. @Override
    13. public void run() {
    14. try {
    15. Thread.sleep(1000);
    16. } catch (InterruptedException e) {
    17. e.printStackTrace();
    18. }
    19. list.add(4);
    20. list.add(5);
    21. }
    22. });
    23. td.start();
    24. td.join();
    25. while(iterator.hasNext()){
    26. System.out.println(iterator.next());
    27. }
    28. System.out.println("------------------");
    29. for(int i=0;i<list.size();i++){
    30. System.out.println(list.get(i));
    31. }
    32. }
    33. }

    并发阻塞队列-SynchronizedQueue

    image.png