本博客根据《Java多线程编程核心技术》和《实战高并发程序设计》总结整理
单例模式是设计模式中使用最为普遍的模式之一。它是一种对象创建模式,用于产生一个对象的具体实例,它可以确保系统中一个类只产生一个实例。在java中,这样的行为能带来两大好处:
单例模式本身与并行没有直接的关系,但是,我们不可避免的会在多线程环境中使用它们。并且,系统中使用单例的地方可能非常频繁,因此我们需要一种高效的单例实现。
立即加载就是使用类的时候已经将对象创建完毕,常见的实现办法就是直接new 实例化。而立即加载从中文看就是“着急,急迫”的意思,所以也叫“饿汉模式”。
这个实现非常简单,但无疑是一个正确并且良好的实现。
public class Singleton {
private Singleton(){
}
private static Singleton instance = new Singleton();
public static Singleton getInstance(){
return instance;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName() + " " +Singleton.getInstance().hashCode());
}).start();
}
}
}
使用这种方式需要注意的是:
private
。private
并且static
的。如果不是private,那么instance的安全性无法保证。一个意外就可能使得instance变为null。其次,因为工厂方法getinstance()必须是static的,因此对应的instance也必须是static。不足之处:
Singleton实例在什么时候创建是不受控制的,对于静态成员instance,它会在类第一次初始化的时候被创建。这个时刻并不一定是getInstance()方法第一次被调用的时候。也就是说这种单例实现方式不能够精确的控制instance的创建时间,如果这种不足不重要,那么这种单例实现方式是一个不错的选择。
延迟加载就是在调用get()方法时实例才会被创建,常见的实现办法就是在get()方法中进行new实例化。这样实现的好处是:
但是坏处也很明显,在多线程的环境中,会存在线程安全问题。
如下代码所示:
public class Singleton {
private Singleton(){
}
private static Singleton instance;
public static Singleton getInstance(){
//延迟加载
if(instance == null){
instance = new Singleton();
}
return instance;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName() + " " +Singleton.getInstance().hashCode());
}).start();
}
}
}
为了防止对象被创建,我们可以使用synchronized关键字进行方法同步,这种实现的好处是,充分利用了延迟加载的特性,只有在真正需要时创建对象,但坏处也很明显,并发环境下加锁,竞争激烈的场合对性能可能产生一定的影响。但总体来说,这是一个非常易于实现和理解的方法。
代码如下:
public class Singleton {
private Singleton(){
}
private static Singleton instance;
public static synchronized Singleton getInstance(){
//延迟加载
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
双重检查加锁机制分为如下两重检查:
这样一来,就只需要在类实例初始化时进行一次同步代码判断即可,而非每次调用getInstance()方法都进行同步判断,大大节省了时间,具体实现如下:
public class Singleton {
/**
* 装载时不创建类实例,但需要利用一个类变量去保存后续创建的类实例
* 添加volatile关键词使其不会被本地线程缓存,保证线程能正确处理
* 添加static关键词使得该变量能在getInstance()静态方法中使用
*/
private volatile static Singleton instance = null;
/**
* 私有化构造方法,使外部无法通过构造方法构造除instance外的类实例
* 从而达到单例模式控制类实例数目的目的
*/
private Singleton() {
}
/**
* 类实例的全局访问方法
* 添加static关键词使得外部可以通过类名直接调用该方法获取类实例
* @return 单例类实例
*/
public static Singleton getInstance() {
// 第一重检查:如果instance未被初始化,则进入同步代码块
if (instance == null) {
// 同步代码块,保证线程安全
synchronized (Singleton.class) {
// 第二重检查:如果instance未被初始化,则初始化该类实例
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
问题:
在《实战Java高并发程序设计》中,作者提到,这种方式实现单例模式是一种非常丑陋、复杂的方法,甚至在低版本的JDK中都不能保证正确性。因此不推荐大家使用。
这一方案的核心在于Java的类级内部类(即使用static关键字修饰的内部类,否则称之为对象级内部类)以及多线程缺省同步锁,先看看具体实现:
public class Singleton {
/**
* 私有化构造方法,使外部无法通过构造方法构造除instance外的类实例
* 从而达到单例模式控制类实例数目的目的
*/
private Singleton() {
}
/**
* 类级内部类,用于缓存类实例
* 该类将在被调用时才会被装载,从而实现了延迟加载
* 同时由于instance采用静态初始化的方式,因此JVM能保证其线程安全性
*/
private static class Instance {
private static Singleton instance = new Singleton();
}
/**
* 类实例的全局访问方法
* 添加static关键词使得外部可以通过类名直接调用该方法获取类实例
* @return 单例类实例
*/
public static Singleton getInstance() {
return Instance.instance;
}
}
首先,getInstance()方法中没有锁,这使得在高并发环境下性能优越。其次,只有在getInstance()方法被第一次调用时,Singleton对象才会被创建。因为这种方法巧妙的使用了内部类和类的初始化方式。内部类Singleton被声明为private,这使得我们不可能在外部访问并初始化它。而我们只可能在getInstance()内部对Singleton类进行初始化,利用虚拟机的类初始化机制创建单例。