定义
单例模式(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>(){@Overrideprotected ThreadLocalSingleton initialValue() {return new ThreadLocalSingleton();}};private ThreadLocalSingleton(){}public static ThreadLocalSingleton getInstance(){return threadLocalInstance.get();}}
ThreadLocal 将所有的对象全部放在 ThreadLocalMap 中,为每个线程都提供一个对象,实际上是以空间换时间来实现线程间隔离的。
总结:
单例模式可以保证内存中只有一个实例,减少了内存开销,可以避免对资源的多重占用!
本文详细介绍了单例模式的概念、适用场景及多种实现方式,包括饿汉式、懒汉式、注册式和ThreadLocal单例等,并分析了每种实现方式的特点。
342

被折叠的 条评论
为什么被折叠?



