假设我声明如下:
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");
}
}
答案 0 :(得分:6)
使用布尔作为锁是一个非常糟糕的主意:您实际上正在使用全局变量Boolean.TRUE/FALSE
,代码的任何其他部分都可以访问该变量并可能使代码死锁。
使用非最终变量作为锁是一个更糟糕的想法:每次重新分配实例(isCondition = true
)时都会更改锁定,这意味着两个线程可能同时执行同步块,哪种失败整个想法。
所以我会推荐一个标准的习语:
private static final Object lock = new Object();
private static boolean isCondition;
synchronised(lock) {
isCondition = true;
// ...
}
答案 1 :(得分:3)
我认为这里的大多数其他答案都不完全正确。由于你没有包含initializeTouchPointService
的代码,所以有点难以理解你在做什么,但是你似乎正在做一些改变" Double Checked Locking"成语。
它是difficult to get this concurrency idiom right,如果您在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");
}
}
答案 2 :(得分:1)
不是一个完整的答案,但是:这里有几个人说过“你不能使用布尔作为锁,因为......”
这些解释使应该是一个简单的想法变得复杂。当您编写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 ) {
...
}
答案 3 :(得分:0)
正如其他人在评论中建议的那样,您可以同步其他内容并避免此问题。 定义要锁定的新变量:
private final Object lock;
现在改变你的代码:
synchronized(lock) {
isCondition = true;
somestuff();
}
通过在同步方法中使用所有这些功能,您还可以在没有变量的情况下实现类似的功能。