zoukankan      html  css  js  c++  java
  • JUC详解

    一、Java多线程 -- JUC包源码分析1 -- CAS/乐观锁

      乐观锁其实就是不加锁,用CAS + 循环重试,实现多个线程/多个客户端,并发修改数据的问题

      使用AtomicStampedReference类下的 

      public boolean compareAndSet(V expectedReference, //旧值 V newReference, //新值 int expectedStamp, //旧版本号 int newStamp //新版本号)

      方法可实现原子性的CAS

      –上面的atomicRef.compareAndSet(..)的第一个参数,传入的是一个ReferenceIntegerPair对象,它里面包含了2个字段:值 + 版本号。这也就意味着,它同时比较了值和版本号。 
      – 值不等,则肯定被其他线程改过了,不用再比较版本号,cas提交失败; 
      值相等,再比较版本号,如果版本号也相等,则说明真的没有被改过,cas提交成功; 
      值相等,版本号不等,则就是出现了ABA,cas提交失败。

    二、Java多线程 -- JUC包源码分析2 -- Copy On Write/CopyOnWriteArrayList/CopyOnWriteArraySet

    CopyOnWrite, 
    顾名思义:就是在Write的时候,不直接Write源数据,而是把数据Copy一份出来改,改完之后,再通过悲观锁或者乐观锁的方式写回去。

    CopyOnWrite的主要目的是实现“写”加锁,“读”不加锁,从而提高并发度。在上面例子中,CopyOnWriteArrayList使用了CopyOnWrite + 悲观锁; NumberRange使用了CopyOnWrite + 乐观锁。

    CopyOnWriteArraySet就是用Array实现的一个Set,保证所有元素都不重复。其内部就是封装的一个CopyOnWriteArrayList

    三、Java多线程 -- JUC包源码分析3-- volatile/final语义

    -volatile应用1 – 内存可见性 – JMM内存模型 

    线程A,线程B有各自的local内存。在把变量从主内存读到自己的工作内存,修改之后,不一定会立即写入主存,因此另一个线程不可见。

    要保证上述案例可以完全正确执行,需要在变量前加volatile。

    volatile变量可以保证:每次对该变量的写,必定刷回到主存;每次对该变量的读,必定从主存读取。从而可以保证,一个线程对共享变量的写,对其他线程可见。

    -volatile应用2 – 原子性 

    由于JMM并不要求对一个64位的long/double型的变量写入具有原子性,在32位的机器上,对一个long型变量的写入,可能会分成高32位,低32位2次写入。此时,另一个线程去读取时,可能读到“写了一半”的无效值!

    要解决上述问题,可以加锁,也可以加volatile关键字。

    可见,在对单个变量的读写中,volatile变量起到了锁同样的作用。

    也正因为如此,在AtomicInteger/AtomicLong中,其get()/set()函数,都未加锁,却是线程安全的!!


    -volatile应用3 – 构造函数逸出/DCL问题(Double Checking Locking) 

    线程安全的单例模式中,有一种经典写法,即DCL(Doule Checking Locking),如下所示:

    public class Sington
    {
    private static Sington instance;
    
    public static Sington getInstance()
    {
      if(instance == null)                //DCL
      {
        synchronized(Sington.class)       
        {
          if(instance == null)
             instance = new Instance();   //有问题的代码!!!
        }
      }
    
      return instance;
    }

    上述的new Instance(),底层可以分为3个操作: 分配内存,在内存上初始化成员变量,把instance指向内存。

    这3个操作,可能重排序,即先把instance指向内存,再初始化成员变量。

    此时,另外一个线程就会拿到一个未完全初始化的对象。这时直接访问里面的成员变量,就可能出错。而这就是典型的“构造函数溢出”问题。

    要解决此问题,只要在instance前加volatile就可以了!

    当然,还有另外1种经典的线程安全的单例模式 – 基于类加载器的方案

    public class Instance
    {
      private static class InstanceHolder
      {
        public static Instance instance = new Instance();
      }
    
      public static Instance getInstance()
      {
          return InstanceHolder.instance;
      }
    }


    -final应用1 – 避免构造函数重排序 

    答案是:a, b 未必一定等于1,2。因为这里的i, j都是非volatile变量,线程A的重排序,可能使得i, j的赋值,在构造函数之后执行!!也就是说,线程B拿到obj的时候,obj的i, j变量可能赋值还未完成!

    解决办法是:给i, j 加上final,final的语义: 保证final变量的初始化,一定在构造函数返回之前完成!


    -final应用2 – CopyOnWrite 

    在上1篇 NumberRange例子中,我们看到lower, power都是final类型,这也确保了lower, power只可能被赋值1次。后续要想再改变值,只能拷贝一份出来改!

    所以,通常应用CopyOnWrite的地方,也会相应的使用final!


    -atomic数组/volatile数组/final数组


    -指令重排序,happen before语义

    从上述各种案例可以看出,问题主要出在“指令重排序”上。

    为什么要指令重排序呢?

    从程序员角度来讲,最好是不要有任何的指令重排,这样程序最容易理解;但从CPU和编译器角度,希望在不改变单线程程序语义的情况下,尽可能的重排序,最大程度的提高执行效率。

    而对于多线程程序,因为重排序导致的线程之间的不同步,则由程序员自己处理!

    volatile和final的底层原理,就是一定程度上禁止重排序,从而实现多线程程序的同步。

    四、Java多线程 -- JUC包源码分析4 -- 各种锁与无锁

     未看懂。。。。。。。。

    五、Java多线程 -- JUC包源码分析5 -- Condition/ArrayBlockingQueue/LinkedBlockingQueue/Deque/PriorityBl

    参考:http://blog.csdn.net/chunlongyu/article/category/6399716

  • 相关阅读:
    P4127 [AHOI2009]同类分布
    区间DP
    P3146 [USACO16OPEN]248
    P1241 括号序列
    P2858 [USACO06FEB]奶牛零食Treats for the Cows
    P2602 [ZJOI2010]数字计数&P1239 计数器&P4999 烦人的数学作业
    数位DP
    jquery生成元素注册事件无效,及事件委托的使用
    Jquery ajax运用执行顺序有误怎么解决
    html页面输入框input的美化
  • 原文地址:https://www.cnblogs.com/meituan/p/7954442.html
Copyright © 2011-2022 走看看