关于单例类和线程的问题

时间:2011-02-09 19:14:11

标签: java singleton

我正在尝试了解单例类以及如何在应用程序中使用它们以保证线程安全。假设您有一个名为IndexUpdater的单例类,其引用如下所示:

 public static synchronized IndexUpdater getIndexUpdater() {
    if (ref == null)
        // it's ok, we can call this constructor
        ref = new IndexUpdater();
    return ref;
}

private static IndexUpdater ref;

让我们假设班级中有其他方法可以完成实际工作(更新指标等)。我想要了解的是如何访问和使用单例将适用于两个线程。让我们假设在时间1中,线程1通过类似这样的调用获得对类的引用:indexUpdater iu = IndexUpdater.getIndexUpdater();然后, 在时间2中,使用引用iu,类中的方法被线程1称为iu.updateIndex。在时间2中会发生什么,第二个线程尝试获取对类的引用。它是否可以执行此操作并且还可以访问单例内的方法,或者只要第一个线程具有对类的活动引用就可以阻止它。我假设后者(或者这将如何工作?)但我想在实施之前确定。

谢谢,

埃利奥特

8 个答案:

答案 0 :(得分:2)

你的假设是错误的。同步getIndexUpdater()只能防止不同的线程在几乎同时调用getIndexUpdater()来创建多个实例。

如果没有同步,可能会发生以下情况:第一个线程调用getIndexUpdater()。 ref为null。线程2调用getIndexUpdater()。 ref仍为null。结果:ref被实例化两次。

答案 1 :(得分:2)

由于getIndexUpdater()是一个同步方法,它只能防止线程同时访问此方法(或由同一个同步器保护的任何方法)。因此,如果其他线程同时访问对象的方法,则可能会出现问题。请记住,如果一个线程正在运行一个synchronized方法,那么试图在同一个对象上运行任何同步方法的所有其他线程都会被阻塞。

更多信息: http://download.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html

答案 2 :(得分:2)

您正在将单例对象的实例化与其使用混为一谈。同步单个对象的创建保证单例类本身是线程安全的。这是一个简单的例子:

public class UnsafeSingleton {
    private static UnsafeSingleton singletonRef;
    private Queue<Object> objects = new LinkedList<Object>();

    public static synchronized UnsafeSingleton getInstance() {
        if (singletonRef == null) {
            singletonRef = new UnsafeSingleton();
        }

        return singletonRef;
    }

    public void put(Object o) {
        objects.add(o);
    }

    public Object get() {
        return objects.remove(o);
    }
}

保证调用getInstance的两个线程获得UnsafeSingleton的相同实例,因为同步此方法可确保singletonRef仅设置一次。但是,返回的实例是非线程安全,因为(在此示例中)LinkedList不是线程安全队列。修改此队列的两个线程可能会导致意外行为。必须采取额外的步骤来确保单例本身是线程安全的,而不仅仅是它的实例化。 (在此示例中,队列实现可以替换为LinkedBlockingQueue,或者getput方法可以标记为synchronized。)

答案 3 :(得分:1)

  

然后,在时间2中,使用引用iu,类中的方法被线程1称为iu.updateIndex。在时间2中会发生什么,第二个线程尝试获取对类的引用。它可以做到这一点,也可以访问单身内的方法......?

答案是肯定的。您对如何获得参考的假设是错误的。第二个线程可以获得对Singleton的引用。 Singleton模式最常用作一种伪全局状态。众所周知,当多个实体使用全局状态时,通常很难处理全局状态。为了使您的单例线程安全,您需要使用适当的安全机制,例如使用原子包装类(如AtomicIntegerAtomicReference(等等))或使用synchronize(或{ {1}})保护关键的代码区域不被同时访问。

答案 4 :(得分:0)

最安全的是使用enum-singleton。

public enum Singleton {
  INSTANCE;
  public String method1() {
    ...
  }
  public int method2() {
    ...
  }
}

线程安全,可序列化,延迟加载等等。只有优势!

答案 5 :(得分:0)

当第二个线程尝试调用getIndexUpdater()方法时,它将尝试获取在您使用synchronized关键字时为您创建的所谓 lock 。但由于其他一些线程已经在方法中,因此它早先获得了锁定,而其他线程(如第二个线程)必须等待它。

当第一个线程完成其工作时,它将释放锁定,第二个线程将立即接受并输入方法。总而言之,使用synchronized始终只允许一个线程进入受保护的块 - 非常严格的访问。

答案 6 :(得分:0)

静态同步保证一次只有一个线程可以在此方法中,并且任何其他尝试访问此方法的线程(或此类中的任何其他静态同步方法)都必须等待它完成。

恕我直言,实现单例的最简单方法是使用一个具有一个值的枚举

enum Singleton {
    INSTANCE
}

这是线程安全的,只有在访问类时才会创建INSTANCE。

答案 7 :(得分:0)

只要您的synchronized getter方法返回IndexUpdater实例(无论它刚刚创建或已经存在无关紧要),都可以从另一个线程中自由调用它。您应该确保您的IndexUpdater是线程安全的,因此可以一次从多个线程调用它,或者您应该为每个线程创建一个实例,这样它们就不会被共享。