单例模式是一种创建设计模式,确保类只有一个实例,同时为此实例提供全局访问点,换句话说就是在JVM中,某个类只允许被创建一次(唯一实例),之后所有的操作都是基于同一个实例。
单例模式同时解决了两个问题(保证类只有一个实例、提供全局访问点),所以违反了单一职责原则。

单例模式主要用来确保某个类的实例只能有一个。
各种Mgr和各种Factrory都可以使用单例模式。
单例实现需要满足以下两点:
/**
* 饿汉式
* 优点:类加载到内存后,就会实例化对象,JVM保证线程安全。
* 缺点:没有达到懒加载的目的。
* 总结:推荐使用,简单易用,至于懒加载,项目中你不使用它为什么要装载它。
*/
public class Singleton {
/**
* 初始化对象,也可以在static代码块初始化
*/
private static final Singleton INSTANCE = new Singleton();
/**
* 私有化构造器,防止new
*/
private Singleton() {}
/**
* 获取实例对象
*
* @return 实例对象
*/
public static Singleton getInstance() {
return INSTANCE;
}
public static void main(String[] args) {
//测试
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);
}
}
/**
* 懒汉式(非线程安全)
* 优点:达到了懒加载,按需加载。
* 缺点:多线程下不安全。
* 总结:不推荐,多线程下不安全。
*/
public class Singleton {
private static Singleton INSTANCE = null;
private Singleton() {}
public static Singleton getInstance() {
if (null == INSTANCE) {
try {
//模拟延迟,多线程下同时进入此代码块。
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Singleton();
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
//通过hashCode打印,可以发现多线程下是不安全的
System.out.println(Singleton.getInstance().hashCode());
}).start();
}
}
}
/**
* 懒汉式(线程安全)
* 优点:达到了懒加载,按需加载,解决了多线程下安全问题。
* 缺点:通过synchronized解决,效率下降。
* 总结:不推荐。
*/
public class Singleton {
private static Singleton INSTANCE = null;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (null == INSTANCE) {
try {
//模拟延迟,多线程下同时进入此代码块。
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Singleton();
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
//通过hashCode打印,可以发现多线程下是安全的
System.out.println(Singleton.getInstance().hashCode());
}).start();
}
}
}
/**
* 双重检查
* 优点:达到了懒加载,按需加载,使用双重检查能够减小锁机制带来的开销,解决了多线程下安全问题。
* 缺点:实现略微复杂,使用volatile保证安全。
* 总结:不推荐。
*/
public class Singleton {
/**
* 使用volatile通过内存屏障禁止指令重排序从而达到线程安全。
*/
private static volatile Singleton INSTANCE = null;
private Singleton() {
}
public static Singleton getInstance() {
if (null == INSTANCE) {
synchronized (Singleton.class) {
if (null == INSTANCE) {
try {
//模拟延迟,多线程下同时进入此代码块。
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
//通过hashCode打印,可以发现多线程下是安全的
System.out.println(Singleton.getInstance().hashCode());
}).start();
}
}
}
new 对象并不是一个原子操作,new 对象时会有三个步骤:
其中1永远是第一步因为2,3都依赖于1,而2,3可能发生指令重排。
在多线程下,线程A进入,调用了INSTANCE = new Singleton(),假设率先执行的是步骤3,此时其他线程进来,发现INSTANCE不为NULL,就会直接返回,产生错误。
/**
* 饿汉式
* 优点:类加载到内存后,就会实例化对象,JVM保证线程安全。
* 缺点:没有达到懒加载的目的。
* 总结:推荐使用,简单易用,至于懒加载,项目中你不使用它为什么要装载它。
*/
public class Singleton {
/**
* 初始化对象
*/
private static final Singleton INSTANCE = new Singleton();
/**
* 私有化构造器,防止new
*/
private Singleton() {
}
/**
* 获取实例对象
*
* @return 实例对象
*/
public static Singleton getInstance() {
return INSTANCE;
}
public static void main(String[] args) {
//测试
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2);
}
}
/**
* 枚举方式
* 优点:不仅保证了线程安全,还防止了序列化。
* 缺点:没啥缺点。
* 总结:最优。
*/
public class Singleton {
private Singleton() {
}
private enum SingletonHelper{
INSTANCE;
SingletonHelper() {
singleton = new Singleton();
}
private final Singleton singleton;
private Singleton getInstance() {
return singleton;
}
}
public static Singleton getInstance() {
return SingletonHelper.INSTANCE.singleton;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
//通过hashCode打印,可以发现多线程下是安全的
System.out.println(Singleton.getInstance().hashCode());
}).start();
}
}
}
以上几种单例模式实现方式中,除了枚举方式外,其他几种方式都可以通过序列化和反序列化绕过类的private构造方法从而创建出多个实例(实际开发中也不会有人去这么做,费力不讨好)。