Java 为什么这种同步在给定场景中不起作用?
在方法3中,它没有为两个对象提供相同的hashCode,我将Thread.sleep和对象实例化都保存在同步块下,所以我想,第二个线程甚至不应该在第一个完成之前进入这个块,但它仍然在做并创建第二个对象,导致diff hashCode。我在这里说什么?有人能纠正我的理解吗?如果我检查空b4对象创建,那么它的工作正如预期,但为什么我需要再次检查空,因为我的整个代码都在同步块下Java 为什么这种同步在给定场景中不起作用?,java,multithreading,concurrency,singleton,Java,Multithreading,Concurrency,Singleton,在方法3中,它没有为两个对象提供相同的hashCode,我将Thread.sleep和对象实例化都保存在同步块下,所以我想,第二个线程甚至不应该在第一个完成之前进入这个块,但它仍然在做并创建第二个对象,导致diff hashCode。我在这里说什么?有人能纠正我的理解吗?如果我检查空b4对象创建,那么它的工作正如预期,但为什么我需要再次检查空,因为我的整个代码都在同步块下 package singleton; public class SingletonClass { private
package singleton;
public class SingletonClass {
private static SingletonClass singleton = null;
private SingletonClass() {
}
static boolean stopThread = true;
//approach 1 which fails in multithereaded env
/*public static SingletonClass getInstance(){
if(null == singleton){
try {
if(stopThread){
stopThread = false;
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
singleton = new SingletonClass();
}
return singleton;
}*/
//approach 2 which works
//method is synchronized
/* public static synchronized SingletonClass getInstance(){
if(null == singleton){
try {
if(stopThread){
stopThread = false;
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
singleton = new SingletonClass();
}
return singleton;
}*/
***//approach 3 which is failing but I don't understand why
//big block of code is synchronized
public static SingletonClass getInstance(){
if(null == singleton){
synchronized (SingletonClass.class){
try {
if(stopThread){
stopThread = false;
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
singleton = new SingletonClass();
}
}
return singleton;
}***
//small block of code is synchronized, checked null again because even object instantiation is synchronised
//if we don't check null, it will create new object once again
//approach 4 which works
/* public static SingletonClass getInstance(){
if(null == singleton){
try {
if(stopThread){
System.out.println("in thread...");
stopThread = false;
//even if we interchange above 2 lines it makes whole lot of difference
//till the time it takes to print "in thread"
//2nd thread reaches there n enters if(stopThread) block because
//stopThread is still true because 1st thread spent time in writing that sentence and
//did not set stopThread = false by the time 2nd thread reached there
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (SingletonClass.class){
System.out.println("in this block");
if(null == singleton){
singleton = new SingletonClass();
}
}
}
return singleton;
}*/
}
---------------------------------------------------------
package singleton;
public class ThreadUsage implements Runnable {
@Override
public void run() {
SingletonClass singletonOne = SingletonClass.getInstance();
System.out.println(singletonOne.hashCode());
}
}
----------------------------------------------------------------
package singleton;
class ThreadUsageTest {
public static void main(String[] args) {
Runnable runnableOne = new ThreadUsage();
Runnable runnableTwo = new ThreadUsage();
new Thread(runnableOne).start();
new Thread(runnableTwo).start();
}
}
---------------------------------------------------------------------------
以下是代码(方法3)最终为单例创建并返回两个(或更多)独立对象的一种方式:
- 线程A进入函数并看到
singleton的
null
- 线程B进入函数并看到
singleton的
null
- 线程A进入同步块
- 线程B等待,因为它无法进入同步块
- 线程A分配给
singleton
- 线程A退出同步块
- 线程A返回一个对象
- 线程B进入同步块
- 线程B分配给
singleton
- 线程B返回一个不同的对象
null
检查与输入其后面的同步块之间存在间隙
要解决此问题,只需将getInstance
设置为synchronized
方法,并删除其中的synchronized
块:
if(null == singleton)
singleton = new SingletonClass();
或者,如果您确实希望避免在Java 5或更高版本(希望您正在使用!)上进行后续调用的同步,请声明singleton
volatile
并在synchronized
块中再次检查:
public static synchronized SingletonClass getInstance() {
if (instance == null) {
singleton = new SingletonClass();
}
return singleton;
}
这就是双重检查锁定习惯用法。在Java4(又名1.4)和更早的版本中,这并不一定可靠,但现在已经可靠了(前提是您在成员上使用volatile
)
在评论中,用户2683814提出了一个很好的问题:
在第二个代码段中的null检查之前,您能解释一下对局部变量的赋值吗?直接检查类变量不起作用 是的,它会起作用,但效率较低 如果
singleton
为非null
,则使用本地方式,该方法仅访问singleton
一次。如果代码没有使用本地,它将访问单例
至少两次(一次检查,一次返回)。由于访问一个volatile
变量稍微昂贵,因此最好使用local(上面代码中的local可以优化为寄存器)
这看起来像是过早的微优化,但如果您不是在性能关键型代码中这样做的,您只需将方法同步化
,并完全避免双重检查锁定的复杂性。:-) 以下是代码(方法3)最终为单例创建并返回两个(或更多)独立对象的一种方式:
- 线程A进入函数并看到
singleton的
null
- 线程B进入函数并看到
singleton的
null
- 线程A进入同步块
- 线程B等待,因为它无法进入同步块
- 线程A分配给
singleton
- 线程A退出同步块
- 线程A返回一个对象
- 线程B进入同步块
- 线程B分配给
singleton
- 线程B返回一个不同的对象
null
检查与输入其后面的同步块之间存在间隙
要解决此问题,只需将getInstance
设置为synchronized
方法,并删除其中的synchronized
块:
if(null == singleton)
singleton = new SingletonClass();
或者,如果您确实希望避免在Java 5或更高版本(希望您正在使用!)上进行后续调用的同步,请声明singleton
volatile
并在synchronized
块中再次检查:
public static synchronized SingletonClass getInstance() {
if (instance == null) {
singleton = new SingletonClass();
}
return singleton;
}
这就是双重检查锁定习惯用法。在Java4(又名1.4)和更早的版本中,这并不一定可靠,但现在已经可靠了(前提是您在成员上使用volatile
)
在评论中,用户2683814提出了一个很好的问题:
在第二个代码段中的null检查之前,您能解释一下对局部变量的赋值吗?直接检查类变量不起作用 是的,它会起作用,但效率较低 如果
singleton
为非null
,则使用本地方式,该方法仅访问singleton
一次。如果代码没有使用本地,它将访问单例
至少两次(一次检查,一次返回)。由于访问一个volatile
变量稍微昂贵,因此最好使用local(上面代码中的local可以优化为寄存器)
这看起来像是过早的微优化,但如果您不是在性能关键型代码中这样做的,您只需将方法
同步化
,并完全避免双重检查锁定的复杂性。:-) 在方法三中,检查singleton
变量;您在任何同步块的外部执行此操作,这就是它不工作的原因:这里不能保证线程在检查之前等待。它们都以最快的速度进行检查,这就是为什么2+个线程都可能在这里看到null,即使其中一个线程已经在创建该实例
然后同步,当然。然而,这并不能神奇地赋予此代码“仅分配singleton一次”的权力——毕竟,该singleton块中的代码将把新创建的SingletonClass
实例分配给singleton
变量
<
public class SingletonClass {
private SingletonClass() {}
public static SingletonClass getInstance() {
return Inner.instance;
}
private static class Instance {
private static final SingletonClass instance = new SingletonClass();
}
}
package singleton;
public class SingletonClassSecond {
private static SingletonClassSecond singleton = new SingletonClassSecond();
private SingletonClassSecond() {
}
public static SingletonClassSecond getInstance(){
return singleton;
}
}
---------------------------------------------------------------------------
package singleton;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
class SingletonClassTest {
@Test
public void shouldCreateSingleton(){
SingletonClass singletonOne = SingletonClass.getInstance();
SingletonClass singletonTwo = SingletonClass.getInstance();
singletonOne.print("1");
singletonTwo.print("2");
Assertions.assertEquals(singletonOne.hashCode(),singletonTwo.hashCode());
}
}
--------------------------------------------------------------------------------
package singleton;
class ThreadUsageTest {
public static void main(String[] args) {
Runnable runnable = new ThreadUsageSecond();
Runnable runnableTwo = new ThreadUsageSecond();
Runnable runnableThree = new ThreadUsageSecond();
Runnable runnableFour = new ThreadUsageSecond();
new Thread(runnable).start();
new Thread(runnableTwo).start();
new Thread(runnableThree).start();
new Thread(runnableFour).start();
}
}