【问题标题】:Difference between volatile Boolean and Booleanvolatile布尔值和布尔值的区别
【发布时间】:2014-07-02 12:56:46
【问题描述】:

假设我这样声明:

 private static  Boolean isCondition = false;

然后我在同步语句中使用如下:

synchronized(isCondition){
               isCondition = true;
               somestuff();
             }

我的问题是,如果我更新isCondition,那么由于自动装箱,它将获得一个新的引用,如果新线程将进入同步块,那么它们将锁定新对象进入同步块。这我不想发生。

所以请给我建议替代方案,如果我使用volatile,那么它将如何防止这种情况,如下所示:

private static volatile Boolean isCondition = false;

实际代码是这样的:

package com.test.spring.utils;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.support.ClassPathXmlApplicationContext;

 /**
  * @author Pratik
*/
public class TouchPointsSpringContext implements ApplicationContextAware
 {
 private static final Log g_log = LogFactory.getLog(TouchPointsSpringContext.class);
 private static ApplicationContext CONTEXT;
 private static volatile Boolean isServiceInitialized = false;

/**
 * This method is called from within the ApplicationContext once it is done
 * starting up, it will stick a reference to itself into this bean.
 *  
 * @param context
 *            a reference to the ApplicationContext.
 */
public void setApplicationContext(ApplicationContext context) throws BeansException
{
    CONTEXT = context;
}

private static void initializeTouchPointService()
{
    g_log.info("getting touchpoints service application context");
    String[] locations =
            { "appContext-main.xml", "appContext-hibernate.xml" };
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext(locations);
    g_log.info("setting touchpoints service application context");
    CONTEXT = applicationContext;
}
 /**
 * This is about the same as context.getBean("beanName"), except it has its
 * own static handle to the Spring context, so calling this method
 * statically will give access to the beans by name in the Spring
 * application context. As in the context.getBean("beanName") call, the
 * caller must cast to the appropriate target class. If the bean does not
 * exist, then a Runtime error will be thrown.
 * 
 * @param beanName
 *            the name of the bean to get.
 * @return an Object reference to the named bean.
 */
public static Object getBean(String beanName)
{
    if (!isServiceInitialized || (CONTEXT == null))
    {
        synchronized (isServiceInitialized)
        {
            if (!isServiceInitialized)
            {
                initializeTouchPointService();
                isServiceInitialized = true;
            }
        }
    }
    return CONTEXT.getBean(beanName);
}

public static void main(String[] args)
{
    TouchPointsSpringContext.getBean("lookupService");
}

}

【问题讨论】:

  • 我会找到其他要同步的东西。
  • 我会把这个同步块移动到一个单独的方法中,并使这个方法同步,基本上就是 Sam 所说的。
  • 感谢您的回复,但我的要求就是这样,我不能冒险让我的整个方法同步或制作额外的方法。

标签: java multithreading volatile


【解决方案1】:

使用布尔值作为锁是一个非常糟糕的主意:您实际上是在使用全局变量Boolean.TRUE/FALSE,您的代码的任何其他部分都可以访问它并可能使您的代码死锁。

使用非 final 变量作为锁是一个更糟糕的主意:每次重新分配实例 (isCondition = true) 时都会更改锁,这意味着两个线程可能会同时执行同步块,这会破坏整体想法。

所以我会推荐一个标准的成语:

private static final Object lock = new Object();
private static boolean isCondition;

synchronised(lock) {
    isCondition = true;
    // ...
}

【讨论】:

  • 感谢 Assylias 的回复,但我在这里谈论的是 volatile boolean 添加的示例代码。您能否看一下并建议在这种情况下 volatile 将如何提供帮助。
  • 由于在示例代码中使用了双重检查锁定,此答案仅部分正确。
  • @flamingpenguin 不确定我是否理解您的评论 - 答案适用于 OP 发布的附加代码。
  • 问题是关于何时使用volatile,并且OP的代码示例包含双重检查锁定,因此正确使用volatile至关重要。尽管您所写的内容确实正确识别并修复了 OP 代码中的错误,但我认为它实际上并没有解决 OP 的问题(直到编辑问题后才清楚),我认为这与延迟初始化和易变的。
【解决方案2】:

我认为这里的大多数其他答案并不完全正确。有点难以理解您在做什么,因为您没有包含initializeTouchPointService 的代码,但是您似乎在做一些“双重检查锁定”习语的变体。

它是difficult to get this concurrency idiom right,如果您使用的是Java 5 之前的版本,那么您根本不应该尝试使用这个成语。我假设您使用的是 Java 5+。

您的代码的重要部分是:

private static ApplicationContext CONTEXT;
private static volatile Boolean isServiceInitialized = false;

...

if (!isServiceInitialized || (CONTEXT == null))
{
    synchronized (isServiceInitialized)
    {
        if (!isServiceInitialized)
        {
            initializeTouchPointService();
            isServiceInitialized = true;
        }
    }
}

假设您使用的是 Java 5 或更高版本,您必须在所有相关变量上使用 volatile 才能使此习惯用法正常工作。您还必须重新检查同步块内的完整条件

您不能使用布尔值作为锁,因为布尔对象是不可变的,当您将条件从 false 更改为 true 时,您将获得不同的对象。而是对条件使用单独的锁定对象和布尔原语。

    private final Object lock = new Object();
    private volatile boolean isServiceInitialized;
    private volatile ApplicationContext context; 

    public Object getBean(String beanName) {
        if (!isServiceInitialized || context == null) {
            synchronized(lock) {
                if (!isServiceInitialized || context == null) {
                    initializeTouchPointService();
                    isServiceInitialized = true;
               }
            }
        }
        return CONTEXT.getBean(beanName);
    }

然而,最新版本的 Java 中的锁在大多数架构上都具有非常好的性能。因此,使用双重检查锁定习语可能不会使您的程序更快 - 尤其是与调用 getBean 时弹簧反射的速度相比。

代替您的双重检查设计,以下更简单的设计如何避免易失性:

   private final Object lock = new Object();
   private boolean isServiceInitialized;
   private ApplicationContext context; 

   private ApplicationContext context() {
       synchronized(lock) {
            if (!isServiceInitialized || context == null) {
                initializeTouchPointService();
                condition = true;
            }
            return context;
        }
   }

   public Object getBean(String beanName) {
        return context().getBean(beanName);
   }

我还建议尽可能避免使用静态,因为在存在全局变量的情况下编写单元测试可能会很棘手。我会认真考虑是否有任何方法可以更改您的设计以减少或消除您对静态状态的使用。

============编辑

根据我对 OP 试图实现的目标的最佳猜测,也许这会更好。但是,它删除了延迟初始化。因此,如果您的程序有时在不使用 getBean() 方法的情况下引用了这个 TouchPointsSpringContext 类,那么您不想要这个答案。

public class TouchPointsSpringContext
{
  private static final Log g_log = LogFactory.getLog(TouchPointsSpringContext.class);
  private static ApplicationContext CONTEXT = initializeTouchPointService();

  private static ApplicationContext initializeTouchPointService()
  {
      g_log.info("getting touchpoints service application context");
      String[] locations =
        { "appContext-main.xml", "appContext-hibernate.xml" };
      ApplicationContext applicationContext = new ClassPathXmlApplicationContext(locations);
      g_log.info("setting touchpoints service application context");
      return applicationContext;
  }

  public static Object getBean(String beanName) 
  {
      return CONTEXT.getBean(beanName);
  }

  public static void main(String[] args)
  {
      TouchPointsSpringContext.getBean("lookupService");
  }
}

请注意,JVM 会自动确保您的静态 CONTEXT 被初始化一次。

或者,如果您可以避免实现“ApplicationContextAware”(考虑到其余代码,实现它似乎没有必要),但您需要保持惰性初始化,那么这可能会更好:

public class TouchPointsSpringContext
{
  private static final Log g_log = LogFactory.getLog(TouchPointsSpringContext.class);
  private static volatile ApplicationContext CONTEXT;
  private static final Object lock = new Object();

  private static ApplicationContext initializeTouchPointService()
  {
    g_log.info("getting touchpoints service application context");
    String[] locations =
        { "appContext-main.xml", "appContext-hibernate.xml" };
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext(locations);
    g_log.info("setting touchpoints service application context");
    return applicationContext;
  }

  public static Object getBean(String beanName)
  {
    if (CONTEXT == null)
    {
        synchronized (lock)
        {
           if (CONTEXT == null)
           {
              CONTEXT = initializeTouchPointService();
           }
        }
    }
    return CONTEXT.getBean(beanName);
   }

   public static void main(String[] args)
   {
       TouchPointsSpringContext.getBean("lookupService");
   }
}

【讨论】:

  • 感谢您的回复。我搞砸了方法名称整个编辑。实际上initializeService应该是initializeTouchPointService。也在代码中编辑。很抱歉。
  • 请再次查看我的代码并提出其他建议,然后是您之前的答案。
  • @flamingpenguin 很好的答案!我只是想知道CONTEXT 是否需要在您的第一个代码块中是易失性的,因为写入context 和写入isServiceInitialized(程序顺序)之间以及写入isServiceInitialized 和任何之间存在发生之前的关系随后读取它(易失性),它必须在读取context(程序顺序)之前。因此,我相信context 不必是易变的。我说的对吗?
  • 我看不到对上下文的写入,我假设对上下文的写入在 initializeTouchPointService 内?如果是,那么我不确定您为什么需要同时检查 context==null 和 !isServiceInitialized。您可以完全删除“isServiceInitialized”并使用单个易失性“CONTEXT”变量,并将 context==null 检查作为您的条件。
  • 是的,它在 OP 的代码中。好问题 - 为什么要检查 CONTEXT == null?我认为 OP 需要重新考虑这一点。
【解决方案3】:

不是一个完整的答案,但是:这里有几个人说过“你不能使用布尔值作为锁,因为......”

这些解释使应该是一个简单的想法变得复杂。当您编写 synchronized (foo) { ... } 时,您并没有在 变量 foo 上同步,而是在某个 object 上同步,它是 表达式 的结果em>,foo.

你在你的例子中做了这样的事情:

Boolean isCondition = ...;

synchronized(isCondition) {
    isCondition = true;
    ...
}

当线程进入同步块时,它会获取Boolean 类的特定实例的监视器。然后,它要做的下一件事是分配isCondition。相同的变量现在指向不同的实例

当第二个线程尝试进入同一个块时,它会尝试在新实例上进行同步,即使第一个线程仍在该块中,它也会成功。 synchronized 唯一防止的是,它防止两个不同的线程同时在同一个实例上同步。在您的示例中,两个不同的线程在两个 不同的 实例上同步,这是允许的。

永远不要这样做:

synchronized ( foo ) {
    ...
    foo = ...;
    ...
}

一个好的做法是,如果您要在括号中放置一个简单的变量名(这是迄今为止最常见的用例),则将其设为 final 变量。

final MyThingummie myThingummie = new MyThingummie(...);

synchronized ( myThingummie ) {
    ...
}

【讨论】:

  • 感谢詹姆斯的回复,但我也在我的问题中解释了同样的事情。值得怀疑的是,在这种情况下,布尔运算的波动性如何。我的意思是 volatile 对同一变量的影响。是否有帮助,如果有,那么如何,如果没有,那么为什么。
  • @PratikSuman,看看我的例子,我说“永远不要这样做”。在该示例中分配变量 foo 要么是错误的,要么您的代码太阴险而无法生存。假设这是一个错误,那么将 foo 设置为“volatile”并不能解决它。事实上,添加“volatile”可以确定第二个线程将看到 foo 的新值,因此能够在第一个线程离开之前进入同步块。如果你写 "synchronized(foo)..." 那么 foo 应该是 final,而不是 volatile。
【解决方案4】:

正如其他一些人在 cmets 中所建议的那样,您可以在其他东西上同步并避免这个问题。 定义一个要锁定的新变量:

private final Object lock;

现在稍微修改一下你的代码:

synchronized(lock) {
           isCondition = true;
           somestuff();
}

通过将所有这些都放在同步方法中,您也可以在没有变量的情况下实现类似的功能。

【讨论】:

    猜你喜欢
    • 2013-11-04
    • 1970-01-01
    • 2013-04-26
    • 2010-11-20
    • 2023-04-10
    • 2010-11-13
    • 1970-01-01
    • 2011-01-29
    • 2017-04-08
    相关资源
    最近更新 更多