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内存模型规则。没有什么比解锁同步化更好的了。无法获得您的观点“MethodgetCount
和setCount
在两个线程t1中调用,t2是线程安全的,不是吗?”-不,它们不是线程安全的,单例在这方面是完全不相关的。假设两个线程都完成了对Singleton.getInstance()
的调用,该调用返回同一个实例,并且仅在此之后,两个线程同时调用getCount
和setCount
。这些方法访问相同的非易失性变量.count
,而不进行同步,其中一个访问(来自setCount
)是写访问。根据定义,这是一场数据竞赛。我认为这是一个正确的答案。如果对对象实例的引用对其他线程可见(例如使用volatile),那么该对象的成员就不能是线程安全的。“方法getCount
和setCount
在两个线程t1中调用,t2是线程安全的,不是吗?”-不,它们不是线程安全的,单线程在这方面是完全不相关的。假设两个线程都完成了对Singleton.getInstance()
的调用,该调用返回同一个实例,并且仅在此之后,两个线程同时调用getCount
和setCount
。这些方法访问相同的非易失性变量.count
,而不进行同步,其中一个访问(来自setCount
)是写访问。根据定义,这是一场数据竞赛。我认为这是一个正确的答案。如果对对象实例的引用对其他线程可见(例如使用volatile),则该对象的成员不能是线程安全的。