定义
单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点
适用场景
-
确保任何情况下都绝对只有一个实例
常见写法
-
饿汉式单例
-
懒汉式单例
-
注册式单例
-
ThreadLocal单例
饿汉式单例
-
在单例类首次加载时就创建实例
-
绝对线程安全,在线程还没出现以前就实例化了,不可能出现访问安全问题
-
缺点:浪费内存空间
-
优点:没有加任何的锁,执行效率比较高,在用户体验上,比懒汉式更好
实例
public class HungrySingleton {
//先静态、后动态
//先属性、后方法
//先上后下
private static final HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
还有另外一种写法,利用静态代码块
public class HungryStaticSingleton {
private static final HungryStaticSingleton hungrySingleton;
static {
hungrySingleton = new HungryStaticSingleton();
}
private HungryStaticSingleton(){}
public static HungryStaticSingleton getInstance(){
return hungrySingleton;
}
}
懒汉式单例
-
被外部类调用的时候内部类才会加载
-
存在线程安全问题,两个或多个线程调用可能获得不同的结果,或者相同的结果下也可能被覆盖后的结果
-
解决线程安全我们可用synchronized
实例
public class LazySimpleSingleton {
private LazySimpleSingleton(){}
//静态块,公共内存区域
private static LazySimpleSingleton lazy = null;
public synchronized static LazySimpleSingleton getInstance(){
if(lazy == null){
lazy = new LazySimpleSingleton();
}
return lazy;
}
}
但是这样会存在一个问题,在线程数量比较多的情况下,CPU分配压力上升,会导致大批量线程出现阻塞,从而导致程序运行性能下降,所以有了双重检查锁的单例模式
public class LazyDoubleCheckSingleton {
private volatile static LazyDoubleCheckSingleton lazy = null;
private LazyDoubleCheckSingleton(){}
public static LazyDoubleCheckSingleton getInstance(){
if(lazy == null){
synchronized (LazyDoubleCheckSingleton.class){
if(lazy == null){
lazy = new LazyDoubleCheckSingleton();
}
}
}
return lazy;
}
}
我们知道,只要用到synchronized关键字,总归是要上锁的,对程序性能还是存在一定影响的,我们可以换种方式,从类初始化角度来考虑,采用静态内部类的方式:
//史上最牛B的单例模式的实现方式
public class LazyInnerClassSingleton {
//默认使用LazyInnerClassGeneral的时候,会先初始化内部类
//如果没使用的话,内部类是不加载的
private LazyInnerClassSingleton(){
if(LazyHolder.LAZY != null){
throw new RuntimeException("不允许创建多个实例");
}
}
//每一个关键字都不是多余的
//static 是为了使单例的空间共享
//保证这个方法不会被重写,重载
public static final LazyInnerClassSingleton getInstance(){
//在返回结果以前,一定会先加载内部类
return LazyHolder.LAZY;
}
//默认不加载
private static class LazyHolder{
private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}
这种形式兼顾饿汉式的内存浪费,也兼顾synchronized性能问题,完美的屏蔽了这两个缺点,比较完善的一个单例模式实现方式。
注册式单例
-
注册式单例又称为登记式单例,就是将每一个实例都登记到某一个地方,使用唯一的标 识获取实例。
-
有两种写法:一种为容器缓存,一种为枚举登记
先看枚举式单例的写法
public enum EnumSingleton {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumSingleton getInstance(){
return INSTANCE;
}
}
看下序列化破坏单例的测试代码是否对枚举式的有效:
public static void main(String[] args) {
try {
EnumSingleton instance1 = null;
EnumSingleton instance2 = EnumSingleton.getInstance();
instance2.setData(new Object());
FileOutputStream fos = new FileOutputStream("EnumSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(instance2);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("EnumSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
instance1 = (EnumSingleton) ois.readObject();
ois.close();
System.out.println(instance1.getData());
System.out.println(instance2.getData());
System.out.println(instance1.getData() == instance2.getData());
}catch (Exception e){
e.printStackTrace();
}
}
验证发现枚举式单例不会被序列化破坏,究其原因是枚举式单例在静态代码中就给INSTANCE进行了赋值,是饿汉式单例的实现。
容器缓存
public class ContainerSingleton {
private ContainerSingleton(){}
private static Map<String,Object> ioc = new ConcurrentHashMap<String,Object>();
public static Object getInstance(String className){
synchronized (ioc) {
if (!ioc.containsKey(className)) {
Object obj = null;
try {
obj = Class.forName(className).newInstance();
ioc.put(className, obj);
} catch (Exception e) {
e.printStackTrace();
}
return obj;
} else {
return ioc.get(className);
}
}
}
}
容器式写法适用于创建实例非常多的情况下,便于管理,但是线程是不安全的。
ThreadLocal 线程单例
-
ThreadLocal 不能保证其创建的对象是全局唯一,但是能保证在单个线程中是唯一的,天生的线程安全。
public class ThreadLocalSingleton {
private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance =
new ThreadLocal<ThreadLocalSingleton>(){
@Override
protected ThreadLocalSingleton initialValue() {
return new ThreadLocalSingleton();
}
};
private ThreadLocalSingleton(){}
public static ThreadLocalSingleton getInstance(){
return threadLocalInstance.get();
}
}
ThreadLocal 将所有的对象全部放在 ThreadLocalMap 中,为每个线程都提供一个对象,实际上是以空间换时间来实现线程间隔离的。
总结:
单例模式可以保证内存中只有一个实例,减少了内存开销,可以避免对资源的多重占用!