为什么在 Facebook YearClass 中声明非线程 - volatile 参数

Why declaring non thread - volatile parameter in Facebook YearClass

我探索了 Facebook 开源 device-year-class,发现了一些有趣的问题,我想问一下。

这很简单 class 做一些计算和 returns 你设备的年份

public class YearClass {  
.
.

      private volatile static Integer mYearCategory;

          public static int get(Context c) {
            if (mYearCategory == null) {
              synchronized(YearClass.class) { 
                if (mYearCategory == null) {
                  mYearCategory = categorizeByYear(c);
                } 
              } 
            } 
            return mYearCategory;
          }

}

为什么他们检查两次 mYearCategory == null 条件以及为什么这个变量声明为 volatile?它不是从不同的线程初始化的,我们在应用程序生命周期中没有对该值进行更改,我们只是第一次检索它......为什么确保我们 read/write 它 [=23] 如此重要=] 内存,如果没有 volatile 会怎样。还有为什么要synchronized呢?没有其他线程可以更改它的风险,它只是为了阅读。

为什么有static volatile变量?

如果您通过多个线程访问静态值,则每个线程都可以有其本地缓存副本。为避免这种情况,您可以将变量声明为 static volatile,这将强制线程每次读取全局值。

现在来回答你的第二个问题。为什么要检查 null 两次?这称为双重检查锁定优化。

什么是双重检查锁定?

          public static int get(Context c) {
            if (mYearCategory == null) {
              synchronized(YearClass.class) { 
                if (mYearCategory == null) {
                  mYearCategory = categorizeByYear(c);
                } 
              } 
            } 
            return mYearCategory;
          }

考虑一下,对 get(Context c) 的第一次调用将创建对象,并且只有少数几个在此期间尝试访问它的线程需要同步;之后所有的调用都得到了对成员变量的引用。由于在某些极端情况下同步方法可能会使性能降低 100 倍或更高,因此每次调用此方法时获取和释放锁的开销似乎是不必要的:一旦初始化完成,获取和释放锁就会出现不必要。许多程序员尝试通过以下方式优化这种情况:

  1. 检查变量是否已初始化(未获取锁)。如果它被初始化,return它立即。
  2. 获取锁。
  3. 仔细检查变量是否已经初始化:如果另一个线程首先获得了锁,它可能已经完成了初始化。如果是这样,return 初始化变量。
  4. 否则,初始化并 return 变量。