单列模式是为了保证某个对象在程序的生命周期内,在内存中只存在一个实例。
即一个类只有一个对象。
尽量在合适的场合使用单例
使用单例可以减轻加载的负担、缩短加载的时间、提高加载的效率,但并不是所有地方都适用于单例,简单来说,单例主要适用于以下三个方面:
控制资源的使用,通过线程同步来控制资源的并发访问
控制实例的产生,以达到节约资源的目的
控制数据的共享,在不建立直接关联的条件下,让多个不相关的进程或线程之间实现通信
首先是节省内存资源, 对于程序中一些大对象,这些对象占用内存较大, 频繁创建不但内存开销增大,还会耗费很多内存 分配的时间, 另外,会触发频繁的 GC(垃圾回收),GC 又会增加时间开销,情况严重的,可能还会发生 Full GC, 导致 程序主线程阻塞,
如果单例有状态的话,在多线程下,可能线程不安全。
1-不要在controller中定义成员变量。 2-万一必须要定义一个非静态成员变量时候,则通过注解@Scope(“prototype”),将其设置为多例模式。 3-在Controller中使用ThreadLocal变量
通过类加载来保证线程安全
这种方式是基于 java 类加载机制避免了多线程同步的问题,
不过正因为如此,
实例在装载时就实例化,
而且导致实例化的 原因还有很多种,
有时候,我们是不愿意将其实例化的。
而且单例模式大多数实例化都是调用 getInstance() 方法,违背 了懒加载的设计。
class Singleton1{ /* 有多饿,一上来就实例化 初始化的时刻: 1-after 2-读静态字段 3-设置一个静态字段 4-静态方法 */ private static Singleton1 instance = new Singleton1(); private Singleton1() { } public static Singleton1 getInstance() { return instance; } }
通过类加载来保证线程安全
利用了 static 块来实例化对象
使用static来定义静态成员变量或静态代码,
借助Class的类加载机制实现线程安全单例。
class Singleton2 { private static Singleton2 instance = null; static { instance = new Singleton2(); } private Singleton2() { } public static Singleton2 getInstance() { return instance; } }
3-通过静态内部类来实现单例
通过类加载来保证线程安全
使用了lazy-loading。
Singleton类被装载了,但是instance并没有立即初始化。
因为 SingletonHolder类没有被主动使用,
只有显式通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化 instance。
是不是很妙啊
class Singleton3 { private static class SingletonHolder { private static final Singleton3 INSTANCE = new Singleton3(); } private Singleton3() { } public static final Singleton3 getInstance() { return SingletonHolder.INSTANCE; } }
4-使用枚举 线程安全 它不仅能避免多线程同步问题, 而且还能防止反序列化重新创建新的对 象, 可谓是很坚强的壁垒。
enum Singleton4 { INSTANCE; public void whateverMethod() { } }
5-使用cas的思想,来保证单例 CAS是项乐观锁技术, 当多个线程尝试使用CAS同时更新同一个变量时, 只有其中一个线程能更新变量的值,而其它线程都失 败, 失败的线程并不会被挂起,而是被告知这次竞争中失败, 并可以再次尝试 有什么缺点呢: 用CAS的好处在于不需要使用传统的锁机制来保证线程安全, CAS是一种基于忙等待的算法,依赖底层硬件的实现, 相对于锁它没有线程切换和阻塞的额外消耗,可以支持较大的并行度。 CAS的一个重要缺点在于如果忙等待一直执行不成功(一直在死循环中), 会对CPU造成较大的执行开销。
class Singleton5 { // 原子性 private static final AtomicReference<Singleton5> INSTANCE = new AtomicReference<Singleton5>(); private Singleton5() {} public static Singleton5 getInstance() { // 死循环 while (1 == 1) { Singleton5 singleton5 = INSTANCE.get(); if (null != singleton5) { return singleton5; } // 为空 // 如果N个线程同时执行到singleton = after Singleton();的时候, // 会有大量对象创建,很可能导致内存溢出。 singleton5 = new Singleton5(); if (INSTANCE.compareAndSet(null, singleton5)) { return singleton5; } } } }
class Singleton6 { private static Singleton6 instance; private Singleton6() { } public static Singleton6 getInstance() { if (instance == null) { // 比较致命的是,在多线程情况下,会出现多次实例化 Singleton 对象 instance = new Singleton6(); } return instance; } }
7-懒汉 * 懒加载, * 要的时候,才new * 线程安全的单例,但是效率低
class Singleton7 { private static Singleton7 instance; private Singleton7() { } // 这里加锁了,锁类,效率低, public static synchronized Singleton7 getInstance() { if (instance == null) { instance = new Singleton7(); } return instance; } }
通过在 synchronized 的外面增加一层判断,就可以在对象一经创建以 后, 不再进入 synchronized 同步块。 这种方案不仅减小了锁的粒度,保证了线程安全,性能方面也得到了大幅提升
class Singleton8 { /* volatile 可见性 防止指令重排 */ private volatile static Singleton8 singleton; private Singleton8() { } public static Singleton8 getSingleton() { if (singleton == null) { // 可能多个线程走到这里 synchronized (Singleton8.class) { // 可能一个线程释放了锁,其它线程拿到锁又进来了, // 所以还要进行非空判断 if (singleton == null) { /* 这里,防止指令重排 如果说我们不使用的话, singleton = after Singleton(); ,这段代码其实是分为三步: 第一步:分配内存空间 第二步:初始化对象 第三步:将singleton对象指向分配的内存地址 volidate 保证上面三步得以顺序执行, 否则可能出现 第一步分配内存空间,第三步 引用指向内存地址, 此时,另外一个线程B走到第一个if (singleton == null)这里,此时确实不为null, 就可能导致线程B拿到的单例对象 是还没有被初始化,导致程序报错。 */ singleton = new Singleton8(); } } } return singleton; } }
这种只能保证,每个线程是单例 不同线程,是不一样的。
class Singleton9 { private static final ThreadLocal<Singleton9> singleton = new ThreadLocal<Singleton9>() { @Override protected Singleton9 initialValue() { return new Singleton9(); } }; public static Singleton9 getInstance() { return singleton.get(); } private Singleton9() { } }