概述
单例模式(Singleton Pattern)可以保证系统内存中,使用单例模式的类只有一个对象实例。
代码实现
具体实现一般为:构造方法设为私有,只能通过某个方法得到实例。
1、饿汉式
在类加载到内存时,就实例化了一个单例对象,jvm保证线程安全。
无参构造方法用 private 修饰,防止被 new 实例化。
提供 public 方法 getInstance(),让调用方获取唯一的对象实例。
/**
* 1、饿汉式
*/
public class Singleton01 {
private static final Singleton01 instance = new Singleton01();
private Singleton01() {}
public static Singleton01 getInstance() {
return instance;
}
}
测试用例:
public class Main {
public static void main(String[] args) {
// 1、饿汉式
Singleton01 s1 = Singleton01.getInstance();
Singleton01 s2 = Singleton01.getInstance();
System.out.println(s1 == s2); // true
}
}
这里只需要看输出结果是不是 true 就能得知是否是同一个实例了。
总结:线程安全,效率高,但如果该实例从始至终都没被使用过,则会造成内存浪费,可以使用。
2、懒汉式
对于饿汉式的缺点,懒汉式变成了类加载时不初始化,调用的时候再初始化。
/**
* 懒汉式
*/
public class Singleton02 {
private static Singleton02 instance;
private Singleton02() {}
public static Singleton02 getInstance() {
if (instance == null) {
instance = new Singleton02();
}
return instance;
}
}
虽然上面instance进行了判空才初始化,但是多线程情况下,会有线程安全问题。
测试用例:
public class Main {
public static void main(String[] args) {
// 2、懒汉式
for (int i = 0; i < 10; i++) {
new Thread(() -> System.out.println(Singleton02.getInstance().hashCode())).start();
}
}
}
----------结果--------------
913559464
913559464
913559464
524254826
913559464
913559464
913559464
913559464
2104693287
913559464
同一个对象实例的hash值应该一样,这里经过测试,发现确实会产生不同的对象实例。
总结:线程不安全,不要使用。
3、使用synchronized的懒加载
由于普通的懒加载会有线程安全问题,一般这时候就会想到使用synchronize来解决线程安全问题。
/**
* 3、使用synchronized的懒加载
*/
public class Singleton03 {
private static Singleton03 instance;
private Singleton03() {}
public static synchronized Singleton03 getInstance() {
if (instance == null) {
instance = new Singleton03();
}
return instance;
}
}
对于静态方法getInstance(),我们使用synchronized锁住class,来保证线程安全。
测试用例:
public class Main {
public static void main(String[] args) {
// 3、使用synchronized的懒加载
for (int i = 0; i < 10; i++) {
new Thread(() -> System.out.println(Singleton03.getInstance().hashCode())).start();
}
}
}
----------结果--------------
1030610325
1030610325
1030610325
1030610325
1030610325
1030610325
1030610325
1030610325
1030610325
1030610325
经过测试,发现产生的对象实例都是同一个,大家也可以增加循环次数来验证结果。
总结:线程安全,但是synchronized会使效率降低,不建议使用。
ps:有人会企图减小sychronized锁的范围,来增加效率。
public class Singleton03 {
private static Singleton03 instance;
private Singleton03() {}
public static Singleton03 getInstance() {
if (instance == null) {
synchronized (Singleton03.class) {
instance = new Singleton03();
}
}
return instance;
}
}
事实证明这样也会用线程安全问题,多线程环境下完全不能使用。
4、双重检测机制
针对上面的问题,我们可以再多加一次判断来保证线程安全,因为进行了两次判断,所以称为双重检测机制。
/**
* 4、双重检测机制
*/
public class Singleton04 {
private static Singleton04 instance;
private Singleton04() {}
public static Singleton04 getInstance() {
if (instance == null) {
synchronized (Singleton05.class) {
if (instance == null) {
instance = new Singleton04();
}
}
}
return instance;
}
}
总结:线程安全,效率高,可以使用。
5、静态内部类
同饿汉式思想差不多,使用jvm保证线程安全,但因为想实现懒汉式的模式,所以构建了静态内部类。
/**
* 5、静态内部类
*/
public class Singleton05 {
private Singleton05() {}
private static class SingletonHolder {
private static final Singleton05 instance = new Singleton05();
}
public static synchronized Singleton05 getInstance() {
return SingletonHolder.instance;
}
}
JVM将推迟 SingletonHolder 的初始化操作,直到开始使用这个类时才初始化,并且由于通过一个静态初始化来初始化 Singleton,因此不需要额外的同步。
总结:线程安全,懒加载,效率高,推荐使用。
6、枚举
枚举,不是很常见的一种写法。很简洁的一种实现方式,提供了序列化机制,保证线程安全,绝对防止多次实例化,即使是在面对复杂的序列化或者反射攻击的时候。
/**
* 6、枚举
*/
public enum Singleton06 {
instance;
}
调用的时候直接 Singleton06.instance 即可,非常完美的一种方式。