0.简介:单例类表示仅允许一个实例存在
(1)注意点:
- 单例模式构造方法的访问修饰符为
private
; - 双重检查锁模式注意添加
volatile
关键字; - Singleton成员变量和单例获取方法都应该为
static
(2)适用场合
- 需要频繁的进行创建和销毁的对象;
- 创建对象时耗时多或耗费资源多,又经常用到的对象,如频繁访问数据库或文件的对象;
- 工具类对象。
(3)总结
单例模式有五种写法:懒汉、饿汉、双重检验锁、静态内部类、枚举;
单线程场景使用懒汉式第一种写法,多线程场景可使用饿汉式,希望懒加载(lazy initialization)则使用静态内部类或者双重检查锁,如果涉及到反序列化创建对象时应使用枚举方式来实现单例,或为Singleton类增加如下readResolve()方法。
private Object readResolve() throws ObjectStreamException {
return INSTANCE;
}
1.枚举单例
通过Singleton.INSTANCE来访问实例。创建枚举默认就是线程安全的,而且还能防止反序列化导致重新创建新的对象。《Effective Java》中讲到枚举是实现单例的最佳方式,建议在实际项目中单例以枚举方式实现。《Java并发编程实践》中推荐使用静态内部类单例模式。
public enum Singleton {
INSTANCE;
public void myMethod() {
// doSomething;
}
}
2.静态内部类单例
类的静态属性只会在第一次加载类时初始化,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程无法进入。静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。
优点:线程安全,懒加载加载,效率高。
public class Singleton {
private Singleton (){}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
public class Singleton {
private Singleton() {}
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
3.双重检查锁 + volatile
:线程安全;延迟加载;效率较高
第一次判空是为了提高获取单例效率,即获取时不用加锁。
第二次判空是为了防止两个线程都走到第一次判空成立条件下的时候生成两个实例。
加voliatile关键字是为了防止单例对象生成和赋值时重排序。
public class Singleton {
private static volatile Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
4.饿汉式:类加载时就完成实例化(类加载过程JVM会自动加锁,因此保证了单例特性),避免了线程同步问题,但没有达到懒加载效果
public class Singleton{
// 静态常量
private static final Singleton instance = new Singleton(); //类加载时就初始化
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
public class Singleton {
private static Singleton instance;
// 静态代码块
static {
instance = new Singleton();
}
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
5.懒汉式
(1)线程不安全方式:适用于单线程,多线程场景下可能导致不同线程获取到不同对象的情况(单线程适用)
public class Singleton {
private static Singleton instance = null;
private Singleton (){} // 构造方法私有化,外界不能new对象
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
(2)线程安全方式,同步方法:每个线程在想获得类实例的时候,执行getInstance()方法都需要进行同步(效率低)
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
(3)线程安全方式,同步代码块:若两个线程都已经通过判空,则将产生两个对象(有风险)
public class Singleton {
private static Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
singleton = new Singleton();
}
}
return singleton;
}
}
6.容器单例:管理单例对象的容器
public class ContainerSingleton {
private ContainerSingleton() {}
private static Map singletonMap = new HashMap();
// 多线程场景可能存入多个key相同但值不同的数据,换成hashtable对性能损耗严重,频繁存取数据不合适。ConcurrentHashMap待分析
public static void putInstance(String key, Object instance) {
if (!TextUtils.isEmpty(key) && instance != null) {
synchronized (singletonMap) {
if (!singletonMap.containsKey(key)) {
singletonMap.put(key, instance);
}
}
}
}
public static Object getInstance(String key) {
return singletonMap.get(key);
}
}
HashMap不是线程安全,若希望线程安全,即不存在不同线程取到的对象不是同一个,在putInstance方法上加同步锁或者同步锁HashMap。改成HashTable也能保证线程安全,但会影响性能,频繁取值的时候都会有同步锁
p.s. 懒汉式与饿汉式取名由来:把对象比喻为食物。懒:表示等到肚子饿了才去准备食物;饿:表示提前准备好食物,饿了直接吃即可。
参考:单例设计模式之容器单例