Java线程和多线程(五)——单例类中的线程安全

单例模式是最广泛使用的创建模式之一。在现实世界之中,诸如Databae的连接或者是企业信息系统(EIS)等,通常其创建都是受到限制的,应该尽量复用已存在对象而不是频繁创建销毁。为了达到这个目的,开发者通常会通过实现单例模式来创建一个wrapper类,来封装资源,限制其运行时所创建对象的个数。

单例中的线程安全

总的来说,开发者一般会按照如下的方式来创建单例的类:

  1. 使用私有构造函数来避免其它外部引用通过new的方式来创建新的对象引用。
  2. 声明一个该类的私有静态变量为实例。
  3. 提供一个公有的静态方法来返回单例的实例。如果实例还没有初始化的话,就将其初始化后再返回实例。

通过上面的步骤,我写了一个如下的单例类:

package com.sapphire.designpatterns;

public class ASingleton {

    private static ASingleton instance = null;

    private ASingleton() {
    }

    public static ASingleton getInstance() {
        if (instance == null) {
            instance = new ASingleton();
        }
        return instance;
    }
}

在上面的代码中,getInstance()方法不是线程安全的。多线程可以在同一时间访问这个方法,而在最开始的少数线程中,实例没有初始化的时候,多线程可以进入到if代码块来创建多个实例,就破坏了单例模式。

通常来说,有三种方式来让我们保证单例模式的线程安全。

在class加载的时候就创建实例变量

这种实现方式有如下优点:

  • 不需要同步即可实现线程安全
  • 容易实现

但是也有一些缺点:

  • 过早的创建了资源,但是应用可能并不会使用这个资源,造成了资源浪费
  • 调用方式无法传入任何参数的,所以我们无法复用这个类。举例来说,如果我们希望有一个单例类能够处理所有的数据库连接信息,并希望能够传入一些数据库信息的话,我们就无法复用这个单例类了。

同步getInstance()方法

这种方法有如下优点:

  • 保证了线程安全
  • 调用方可以传入任何参数
  • 可以保证延迟初始化

当然这种方法也有一些缺点:

  • 因为使用了同步方法,会锁定资源,令所有客户端的请求会优先请求锁,从而降低了性能
  • 包含了很多非必要的同步,因为一旦实例创建完成,同步就只是浪费了性能而没有任何作用了

在if代码块中使用同步代码块

优点:

  • 保证了线程的安全
  • 调用方可以传递参数
  • 保证了延迟初始化
  • 最小化了同步负载,仅仅在最开始的几个线程请求的时候进行同步操作

淡然也有一定的缺点

  • 需要额外的if条件判断

从三种方法来判断,第三种方式应该是最佳的方式来实现同步了,代码大体如下:

package com.sapphire.designpatterns;

public class ASingleton{

    private static ASingleton instance= null;
    private static Object mutex= new Object();
    private ASingleton(){
    }

    public static ASingleton getInstance(){
        if(instance==null){
            synchronized (mutex){
                if(instance==null) instance= new ASingleton();
            }
        }
        return instance;
    }
}

注意,String对象非常不适合来作为同步的锁,因为String可能是复用的对象,锁定String很容易产生死锁切很难发现,所以这里使用的是Object对象来作为同步锁。

原文地址:https://www.cnblogs.com/qitian1/p/6461531.html