1、饿汉式,线程安全
public class Singleton {
/**
* 优点:写法简单,类装载的时候完成实例化,线程安全
* 缺点:类装载的时候就完成实例化,没有Lazy Loading,如果没有被使用,会造成内存浪费
*/
private static final Singleton INSTANCE = new Singleton();
public static Singleton getInstance(){
return INSTANCE;
}
}
2、饿汉式,线程安全
public class Singleton {
/**
* 与第一种方式类似,只是将实例化过程放在静态块中。不知道算不算一种写法。
* 优点:写法简单,类装载的时候完成实例化,线程安全
* 缺点:类装载的时候就完成实例化,没有Lazy Loading,如果没有被使用,会造成内存浪费
*/
private static Singleton INSTANCE;
static{
if(INSTANCE == null){
INSTANCE = new Singleton();
}
}
public static Singleton getInstance(){
return INSTANCE;
}
}
3、懒汉式,线程不安全
public class Singleton {
/**
* 优点:Lazy Loading。避免多余的内存开销。
* 缺点:线程不安全,多线程环境下容易产生多个实例。
*/
private static Singleton INSTANCE;
public static Singleton getInstance(){
if(INSTANCE == null){
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
4、懒汉式,线程安全(同步方法)
public class Singleton {
/**
* 优点:Lazy Loading。避免多余的内存开销。synchronized线程同步,保证了线程安全。
* 缺点:synchronized将方法串行化,效率太低
*/
private static Singleton INSTANCE;
public static synchronized Singleton getInstance(){
if(INSTANCE == null){
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
5、懒汉式,线程安全(同步代码块)
public class Singleton {
/**
* 优点:Lazy Loading。避免多余的内存开销。改良同步方法效率低的问题。
* 缺点:synchronized同步代码块,无法保证线程同步。如果,线程A在判断INSTANCE == null还未执行完毕,另一个线程已经return出一个实例,那么将会产生多个实例。
*/
private static Singleton INSTANCE;
public static Singleton getInstance(){
if(INSTANCE == null){
synchronized(Singleton.class){
INSTANCE = new Singleton();
}
}
return INSTANCE;
}
}
6、懒汉式,线程安全(双重检查)
public class Singleton {
/**
* 优点:线程安全,Lazy Loading,效率高
* 缺点:使用volatile,jdk1.5版本之前无法完全避免重排序锁导致的问题。jdk1.5之前无法保证线程安全。(现在都java12了, 问题不大)
*/
private static volatile Singleton INSTANCE;
public static Singleton getInstance(){
if(INSTANCE == null){
synchronized(Singleton.class){
if(INSTANCE == null){
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
7、静态内部类,线程安全(推荐使用)
public class Singleton {
/**
* 优点:Lazy Loading,线程安全,写法简单
* 缺点:需要额外处理序列化问题,否则一个序列化的对象实例在每次反序列化后都会创建一个新的实例;传参复杂。
*/
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static final Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
8、枚举,线程安全(推荐使用)
public enum Singleton {
/**
* 线程安全,避免反序列化问题。推荐使用
*/
INSTANCE;
public void getInstance(){
}
// public static void main(String[] args) {
// Singleton.INSTANCE.getInstance();
// }
}
为什么说枚举能保证线程安全,而且能保证单例?
通过javap反编译Singleton枚举。
INSTANCE最终被声明为static final。
JVM类加载过程中,虚拟机会保证一个类的()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的()方法,其他线程都需要阻塞等待,直到活动线程执行()方法完毕。如果在一个类的()方法中有耗时很长的操作,就可能造成多个进程阻塞(需要注意的是,其他线程虽然会被阻塞,但如果执行()方法后,其他线程唤醒之后不会再次进入()方法。同一个加载器下,一个类型只会初始化一次。),在实际应用中,这种阻塞往往是很隐蔽的。
所以枚举既保证了线程的安全性,又保证了实例的单例性。
总结:单例模式使用哪种写法,最终取决于业务的需要(如是否需要频繁的创建或者销毁对象)。推荐静态内部类和枚举两种写法。