单例模式:简单来说一个类只能构建一个对象
特征:
1、私有构造函数
2、静态方法返回单例类的对象
3、保证单例类对象只有一个,要注意多线程场景
4、如果单例对象在反序列化时,不会重新创建对象
1、懒汉式
public class Singleton {
private Singleton() {} //私有构造函数
private static Singleton instance = null; //单例对象
//静态工厂方法
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
为什么这样写呢?我们来解释几个关键点:
1、要想让一个类只能构建一个对象,自然不能让它随便去做new操作,因此Signleton的构造方法是私有的。
2、instance是Singleton类的静态成员,也是我们的单例对象。它的初始值可以写成Null,也可以写成new Singleton()。至于其中的区别后来会做解释。
3、getInstance是获取单例对象的方法。
如果单例初始值是null,还未构建,则构建单例对象并返回。这个写法属于单例模式当中的懒汉模式。
如果单例对象一开始就被new Singleton()主动构建,则不再需要判空操作,这种写法属于饿汉模式。
这两个名字很形象:饿汉主动找食物吃,懒汉躺在地上等着人喂。
实现一个线程安全的单例模式
假设Singleton类刚刚被初始化,instance对象还是空,这时候两个线程同时访问getInstance()方法
因为Instance是空,所以两个线程同时通过了条件判断,开始执行new操作:
这样一来,显然instance被构建了两次。让我们对代码做一下修改:
双重检测机制。
public class Singleton {
private Singleton() {} //私有构造函数
private static Singleton instance = null; //单例对象
//静态工厂方法
public static Singleton getInstance() {
if (instance == null) { //双重检测机制
synchronized (Singleton.class){ //同步锁
if (instance == null) { //双重检测机制
instance = new Singleton();
}
}
}
return instance;
}
}
为什么这样写呢?我们来解释几个关键点:
1、为了防止new Singleton被执行多次,因此在new操作之前加上Synchronized 同步锁,锁住整个类(注意,这里不能使用对象锁)。
2、进入Synchronized 临界区以后,还要再做一次判空。因为当两个线程同时访问的时候,线程A构建完对象,线程B也已经通过了最初的判空验证,不做第二次判空的话,线程B还是会再次构建instance对象。
双重检测失效问题:
new Singleton()时一般 来说有三个步骤
1、分配一块内存
2、在内存上初始化Singleton对象
3、把这块内存地址返回赋值给instance
但经过编译器的优化,2和3的顺序有可能颠倒的,也就是说可能你拿到的instance可能还没有被初始化,访问instance的成员变量就没有发生空指针异常,而volatile可以阻止这种情况的发生。
防止指令重排:
public class Singleton {
private Singleton() {} //私有构造函数
private static volatile Singleton instance = null; //单例对象
//静态工厂方法
public static Singleton getInstance() {
if (instance == null) { //双重检测机制
synchronized (Singleton.class){ //同步锁
if (instance == null) { //双重检测机制
instance = new Singleton();
}
}
}
return instance;
}
}
2、饿汉式
线程安全的单例
class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
3、静态内部类单例
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
/**
* 静态内部类
*/
private static class SingletonHolder {
private static Singleton instance = new Singleton();
}
}
当 Singleton 类加载时,静态内部类 SingletonHolder 没有被加载进内存。只有当调用 getInstance()方法从而触发
SingletonHolder.instance 时 SingletonHolder 才会被加载,此时初始化 instance实例,并且 JVM 能确保 instance只被实例化一次。
做到了延迟加载和线程安全
优点:
1. 不用synchronized ,节省时间。
2. 调用getInstance() 的时候才会创建对象,不调用不创建,节省空间,这有点像懒汉式。
4、枚举单例
public enum SingleTon{
INSTANCE;
public void method(){
//TODO
}
}
枚举在java中与普通类一样,都能拥有字段与方法,而且枚举实例创建是线程安全的,在任何情况下,它都是一个单例。我们可直接以
5、容器实现单例
import java.util.HashMap;
import java.util.Map;
/**
* 容器类实现单例模式
*/
public class SingletonManager {
private static Map<String, Object> objMap = new HashMap<String, Object>();
public static void regsiterService(String key, Object instance) {
if (!objMap.containsKey(key)) {
objMap.put(key, instance);
}
}
public static Object getService(String key) {
return objMap.get(key);
}
}