Java 单件模式中的线程安全,其保持器成员

Java 单件模式中的线程安全,其保持器成员,java,multithreading,synchronized,happens-before,Java,Multithreading,Synchronized,Happens Before,我有以下带有单例模式的示例代码: class Singleton{ private static Singleton instance; private int count; private Singleton(){} public static synchronized Singleton getInstance(){ if(instance==null){ instance=new Singleton(); } return ins

我有以下带有单例模式的示例代码:

class Singleton{
    private static Singleton instance;
  private int count;
  private Singleton(){}
  public static synchronized Singleton getInstance(){
    if(instance==null){
        instance=new Singleton();
    }
    return instance;
  }
  public int getCount(){return count;}
  public void setCount(int count){this.count=count;}
  public static void main(String[] args) throws InterruptedException{
    Thread t1=new Thread(()->{
        while(Singleton.getInstance().getCount()==0){
        //loop
      }
      System.out.println("exist t1 with count="+Singleton.getInstance().getCount());
    });
    t1.start();
    Thread.sleep(1000); //time out to force t1 start before t2
    Thread t2=new Thread(()->{
        Singleton.getInstance().setCount(10000);
    });
    t2.start();
    t1.join();
    t2.join();
  }
}

我有一个问题:方法
getCount
setCount
调用两个线程t1,t2是线程安全的,不是吗

在两个线程t1,t2中调用的getCount/setCount方法是线程安全的,不是吗

如果您打算使t2所做的更改对t1可见-是,则线程2设置的计数对线程t1可见

这是因为线程1每次都通过调用
getInstance
来获取单例实例,这是一种
synchronized
方法。这将建立一个before关系,线程2所做的更改将对线程1可见


但是,如果只将代码更改为调用
getInstance
,并使用该引用调用
getCount
,则另一个线程(t2)所做的更改可能对线程t1不可见,并且它可能会继续循环

 Singleton s = Singleton.getInstance();
    while(s.getCount()==0){
        //loop
    }
要使更改得到反映,您必须将
count
设置为volatile

在两个线程t1,t2中调用的getCount/setCount方法是线程安全的,不是吗

如果您打算使t2所做的更改对t1可见-是,则线程2设置的计数对线程t1可见

这是因为线程1每次都通过调用
getInstance
来获取单例实例,这是一种
synchronized
方法。这将建立一个before关系,线程2所做的更改将对线程1可见


但是,如果只将代码更改为调用
getInstance
,并使用该引用调用
getCount
,则另一个线程(t2)所做的更改可能对线程t1不可见,并且它可能会继续循环

 Singleton s = Singleton.getInstance();
    while(s.getCount()==0){
        //loop
    }
要使更改得到反映,您必须将
count
设置为volatile


在“发生之前”关系中,在同步解锁后,线程
t2
可以看到所有更改,而线程
t1
可以看到所有更改。但是,当线程
t2
调用
Main.getInstance()
时,调用此方法之前的所有更改都将对线程
t1
可见,但是
setCount
在同步解锁后被调用,因此,
count
对线程
t1
是不可见的。t1每次调用
getInstance
时都会进入一个
synchronized
块。但是
t1
只能看到
t2
之前所做的更改
t2
解锁同步化遵循Java内存模型规则。没有什么比解锁同步化更好的了。无法在关系发生之前获取切入点,在同步解锁后,所有更改对线程
t2
可见,而对线程
t1
可见。但是,当线程
t2
调用
Main.getInstance()
时,调用此方法之前的所有更改都将对线程
t1
可见,但是
setCount
在同步解锁后被调用,因此,
count
对线程
t1
是不可见的。t1每次调用
getInstance
时都会进入一个
synchronized
块。但是
t1
只能看到
t2
之前所做的更改
t2
解锁同步化遵循Java内存模型规则。没有什么比解锁同步化更好的了。无法获得您的观点“Method
getCount
setCount
在两个线程t1中调用,t2是线程安全的,不是吗?”-不,它们不是线程安全的,单例在这方面是完全不相关的。假设两个线程都完成了对
Singleton.getInstance()
的调用,该调用返回同一个实例,并且仅在此之后,两个线程同时调用
getCount
setCount
。这些方法访问相同的非易失性变量
.count
,而不进行同步,其中一个访问(来自
setCount
)是写访问。根据定义,这是一场数据竞赛。我认为这是一个正确的答案。如果对对象实例的引用对其他线程可见(例如使用volatile),那么该对象的成员就不能是线程安全的。“方法
getCount
setCount
在两个线程t1中调用,t2是线程安全的,不是吗?”-不,它们不是线程安全的,单线程在这方面是完全不相关的。假设两个线程都完成了对
Singleton.getInstance()
的调用,该调用返回同一个实例,并且仅在此之后,两个线程同时调用
getCount
setCount
。这些方法访问相同的非易失性变量
.count
,而不进行同步,其中一个访问(来自
setCount
)是写访问。根据定义,这是一场数据竞赛。我认为这是一个正确的答案。如果对对象实例的引用对其他线程可见(例如使用volatile),则该对象的成员不能是线程安全的。