java 单例 同步_关于java:单例上的同步访问

我有一个关于单例同步的问题。假设我有一个单例模式,如下所示:

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特别需要外部锁定。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值