[英]Difference between volatile Boolean and Boolean
假设我这样声明:
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");
}
}
使用布尔作为锁是一个非常糟糕的主意:您实际上正在使用全局变量Boolean.TRUE/FALSE
,代码的任何其他部分都可以访问并可能使代码死锁。
使用非最终变量作为锁定是一个更糟糕的想法:每次重新分配实例( isCondition = true
)时,您都会更改锁定,这意味着两个线程可以同时执行同步块,这会破坏整个想法。
所以我会推荐一个标准的习语:
private static final Object lock = new Object();
private static boolean isCondition;
synchronised(lock) {
isCondition = true;
// ...
}
我认为这里的大多数其他答案都不完全正确。 由于你没有包含initializeTouchPointService
的代码,所以有点难以理解你正在做什么,但是你似乎正在做一些“双重检查锁定”习语的变体。
很难获得这种并发习惯用法 ,如果您在5之前使用的是Java版本,那么您根本不应该尝试使用这个习惯用法。 我假设您使用的是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试图实现的最佳猜测,也许这会更好。 但是,它消除了惰性初始化。 因此,如果您的程序有时会引用此TouchPointsSpringContext
类而不使用getBean()方法,那么您不需要此答案。
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");
}
}
不是一个完整的答案,但是:这里有几个人说“你不能使用布尔作为锁,因为......”
这些解释使应该是一个简单的想法变得复杂。 当您编写synchronized (foo) { ... }
,您没有在变量 foo
进行同步,而是在同步某个对象上 ,该对象是表达式 foo
的结果。
您在示例中执行了类似的操作:
Boolean isCondition = ...;
synchronized(isCondition) {
isCondition = true;
...
}
当线程进入该同步块时,它将获取Boolean
类的特定实例的监视器。 然后,它做的下一件事是分配isCondition
。 现在,相同的变量指向不同的实例 。
当第二个线程尝试进入同一个块时,它将尝试在新实例上进行同步,即使第一个线程仍然在块中,它也会成功。 synchronized
防止的唯一事情是,它阻止两个不同的线程同时在同一个实例上同步。 在您的示例中,两个不同的线程在两个不同的实例上同步,这是允许的。
永远不要这样做:
synchronized ( foo ) {
...
foo = ...;
...
}
一个好的做法是,如果你要在parens中放一个简单的变量名(这是迄今为止最常见的用例),那么将它作为final
变量。
final MyThingummie myThingummie = new MyThingummie(...);
synchronized ( myThingummie ) {
...
}
正如其他人在评论中建议的那样,您可以同步其他内容并避免此问题。 定义要锁定的新变量:
private final Object lock;
现在改变你的代码:
synchronized(lock) {
isCondition = true;
somestuff();
}
通过在同步方法中使用所有这些功能,您还可以在没有变量的情况下实现类似的功能。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.