Java中的单例同步
我刚刚遇到在错误的类上执行同步的代码:Java中的单例同步,java,android,Java,Android,我刚刚遇到在错误的类上执行同步的代码: public class Test { public static volatile Test instance = null; public static void setIfNull(Test newInstance) { synchronized (WRONG.class) // should be synchronized (Test.class) { if (new
public class Test
{
public static volatile Test instance = null;
public static void setIfNull(Test newInstance)
{
synchronized (WRONG.class) // should be synchronized (Test.class)
{
if (newInstance == null)
throw new IllegalArgumentException("newInstance must not be null.");
if (instance == null) instance = newInstance;
}
}
}
如果同步整个方法,则不会发生上述错误:
public class Test
{
public static volatile Test instance = null;
public static synchronized void setIfNull(Test newInstance)
{
if (newInstance == null)
throw new IllegalArgumentException("newInstance must not be null.");
if (instance == null) instance = newInstance;
}
}
在我看来,第二段代码比第一段代码更能防止错误
关于上述代码模式,在同步块上使用方法同步是否存在任何陷阱
警告:在上述代码
实例中,
字段未正确封装。作为公共成员,没有任何东西可以阻止外部代码不仅读取它,而且以线程不安全的方式写入它。此代码不应用作适当的线程安全单例示例,因为事实并非如此。1)同步方法和块之间的一个显著区别是,同步块通常会减少锁的范围。由于锁定的范围和性能成反比,所以最好只锁定代码的关键部分。使用synchronized block的一个最好的例子是Singleton模式中的双重检查锁定,我们不锁定整个getInstance()方法,只锁定用于创建Singleton实例的关键代码部分。这大大提高了性能,因为只需要锁定一两次
2) 同步块提供了对锁的细粒度控制,因为您可以使用任意锁为关键段代码提供互斥。另一方面,同步方法始终锁定由该关键字表示的当前对象或类级锁(如果是静态同步方法)
3) 如果作为参数提供给块的表达式的计算结果为null,则同步块可以抛出throw java.lang.NullPointerException,而同步方法则不是这样
4) 对于synchronized方法,锁在线程进入方法时由线程获取,在线程离开方法时由线程释放,无论是正常还是通过抛出异常。另一方面,在同步块的情况下,线程在进入同步块时获得锁,在离开同步块时获得释放
阅读更多信息:我不记得同步整个方法时有什么陷阱。当然,那些只是锁定在特定区域周围的更“昂贵” 如果您不确定我是否会首先使用同步方法,直到遇到瓶颈 要避免阻塞错误的对象,只需创建一个实例变量:
private final Object block = new Object();
当你需要同步时使用这个。无论如何,当您这样做时,请记住,由不同线程调用的其他方法不尊重这一点,您会得到副作用。所以你走这条路的时候要小心。
我读了很多关于这些主题的书,这些书都是一个非常具体的答案,很难被认为是正确的答案
我建议您阅读Brian Goetz的“实践中的Java并发性”
还有来自Angelika Langer、Klaus Kreft(一本使用volatile关键字的书)的“Java核心”(德语书,仍然很好奇没有人将其翻译成英语,因为它是该领域的杰作)
如果愿意,还可以使用重入锁来获得公平锁定
关于上述代码模式,在同步块上使用方法同步是否存在任何陷阱
鉴于此:
public static synchronized void setIfNull(Test newInstance) {
...
}
…与此完全相同():
…您真正要问的是:“在另一个类对象上同步错误的.class
与在上同步此.class
有什么区别?”
唯一需要注意的是代码中是否有其他内容决定在
Test.class
上同步。第二个块不太容易出错,因为您不需要按暗示的方式指定类对象。@PeterLawrey谢谢,这正是我的想法。我还应该注意到其他的差异吗?字节码实际上是不同的,但是这些差异并不重要。@Tomas这不是这个问题的重复,因为我的问题是关于静态类成员的。另外,我问的是一个非常具体的用例,我知道这两种方法都做相同的事情,我只想100%确定我没有遗漏什么。这个答案并没有提供问题的答案。我不是锁定对象,而是锁定类,因为这是静态实例。那么,为什么不简单地锁定整个静态类呢?当我记得静态类被它所拥有的线程完全锁定。“静态类被它所拥有的线程完全锁定”-这句话毫无意义。谢谢你的帮助,但你的回答根本没有解决我的问题。唯一与我的Q有关的部分是第一句话。但我问这个问题只是因为我不是100%确定我没有遗漏什么。有一个基本上是“我也不确定”的答案并不能真正帮助我。我的问题很精确。代码中有静态成员和静态方法,它们非常简短且易于理解。我还注意到,我认为第二段代码比第一段代码好,我的问题是“在同步块(1)上使用方法同步(2)是否存在与上述代码模式相关的陷阱?”我认为这足够精确。我问的是一段非常具体的代码。不过,您的代码中还有许多其他陷阱,但这些不是您所问的,因此我不会深入讨论。我知道我的代码中还有其他陷阱:)这就是为什么我在问题中添加了警告,因此,知识水平较低的开发人员不会在不知道其中存在巨大陷阱的情况下获取并使用该代码。
public static void setIfNull(Test newInstance) {
synchronized (Test.class) {
...
}
}