我有一个关于单例同步的问题。假设我有一个单例模式,如下所示:
public class Singleton {
private final Object obj;
private static Singleton instance;
private Singleton(Object obj) {
this.obj = obj;
}
public static synchronized Singleton getInstance(Object obj) {
if (instance == null) {
instance = new Singleton(obj);
}
return instance;
}
public synchronized void methodA() {
// ...
}
public synchronized void methodB() {
// ...
}
}
我想同步对其的访问。我有两个假设,需要验证。
假设:
由于所有方法(包括初始化程序)都已同步,因此对该Singleton的每次访问都是线程安全和同步的。
假设:
当我想确保要先调用methodA()然后立即methodB()的线程而又没有另一个线程调用单例的方法时,在这样的实例上进行同步是否正确?
Singleton singleton = Singleton.getInstance(obj);
synchronized (singleton) {
singleton.methodA();
singleton.methodB();
}
说明:
1.假设是否正确,因为在非静态方法上同步是在对象本身上同步,并且由于它始终是同一对象,所以访问是同步的?并且对getInstance(obj)的调用在类本身上同步吗?
2.假设是否正确,因为每个线程使用getInstance(obj)都会获得相同的对象,因此同步是正确的,因为另一个线程将等待直到退出同步块(...methodA(); methodB();)为止?
假设我理解正确,您的假设是正确的。我只想指出,在大多数情况下,您可以使用更简单的单例模式:
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
该字段将以线程安全的方式在第一个静态引用(例如getInstance())上初始化,而无需显式同步。
另外,instance应该是final。
在您的示例中,getInstance方法不需要同步。该实例是在访问该类之前创建的,并且无论如何每个线程都将获得相同的实例。
@ralfstx,意味着要删除它。谢谢。
谢谢。在我的原始代码中,我对构造函数进行了一些初始化,然后编辑了帖子。为什么getInstance不必同步?该实例是在调用者调用getInstance(obj)的同时创建的,因此似乎两个线程同时调用getInstance(obj),这会使该线程安全吗?还是我误会了?
@wegsehen就同步而言,JVM自动确保静态初始化的线程安全性。关于新的构造函数,每次对getInstance()的调用都传递一次,这只会分配一次,这似乎很不寻常。
实际上,引入单例的最佳(且并发安全)方法是通过枚举来实现。 http://stackoverflow.com/questions/427902/what-is-the-best-approach-for-using-an-enum-as-a-singleton-in-java
@KrystianLaskowski注意,我没有说这是"最佳"模式,只是它比较简单。不需要"实际上"我。而且,据我所记得,枚举提供了更强的单例保证,而不是更好的线程安全性。
线程安全单例样本;
public class Singleton {
private static Singleton instance;
private Singleton(){
}
public static Singleton getInstance(){
synchronized (Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
return instance;
}
}
你们两个假设都是正确的。但是,对于创建Singleton,我将建议以下方法是100%线程安全的,并且没有同步问题,因为它利用了Java的类加载机制。
像此类一样,Provider将仅被加载一次,因此将仅存在Network类的一个实例,并且由于类不会被加载两次,因此没有两个线程可以在同一JVM中创建2个实例。
public class Network {
private Network(){
}
private static class Provider {
static final Network INSTANCE = new Network();
}
public static Network getInstance() {
return Provider.INSTANCE;
}
//More code...
}
如您所知,synchronized关键字用于获取对
宾语。如果您想在执行之后调用您的自愿方法
一种方法。然后我们可以从同步方法中调用该方法
下面的代码,但线程不会释放,直到它退出
第一个同步块。
public synchronized void method1A() {
// ......
method1B();
}
public synchronized void method1B() {
// ...
}
在这里,线程将获取在同步方法1A中输入的锁
最后,它将调用另一个同步方法1B。线程是
将在method1A执行完成后释放锁。
两者都是对的。
最好的测试方法(测试解决方案总是好的,而不是依赖于假设)将是在调用方法之间等待一段时间,然后从不同的线程调用methodA,然后检查执行顺序。例如:
public synchronized void methodA() {
println("methodA");
}
public synchronized void methodB() {
println("methodB");
}
然后,在测试用例或主要方法中:
new Thread(){
public void run(){
Singleton singleton = Singleton.getInstance();
synchronized (singleton) {
singleton.methodA();
singleton.wait(5000L);//this gives us 5 seconds to call it from different thread
singleton.methodB();
}
}
}.start();
new Thread(){
public void run(){
Singleton.getInstance().methodA();
}
}
}.start();
您的第一个假设是正确的,但是,在专用锁对象上进行同步而不是使用synchronized方法是一种好习惯。这样做可以将并发问题排除在API之外,并允许您稍后使用java.util.concurrent包替换锁,以实现更有效的实现。您还可以防止死锁,因为其他对象无法获取相同的锁对象。
public class Example {
// leaving out the singleton aspect here
// consider java.util.concurrent.locks instead
private Object lock = new Object();
public synchronized void methodA() {
synchronized(lock) {
// ...
}
}
public synchronized void methodB() {
synchronized(lock) {
// ...
}
}
}
客户端仍然可以在实例上同步:
synchronized(instance) {
instance.methodA();
instance.methodB();
}
例如,查看java.util.Collections类中SynchronizedCollection的实现,该实现也使用内部锁定对象。
与单例同步有关的一个问题:如果您的应用程序中有多个并发线程(例如,在Web应用程序中),则由于所有线程都必须互相等待,因此此中央锁很快就会成为性能瓶颈。
OP特别需要外部锁定。