#yyds干货盘点#设计模式之单例模式

【#yyds干货盘点#设计模式之单例模式】满堂花醉三千客,一剑霜寒十四洲。这篇文章主要讲述#yyds干货盘点#设计模式之单例模式相关的知识,希望能为你提供帮助。
作者:汤圆
个人博客:javalover.cc
前言有时候我们的类并不需要很多个实例,在程序运行期间,可能只需要一个实例就够了,多了反而会出现数据不一致的问题;
这时候我们就可以用单例模式来实现,然后程序中所有的操作都基于这个实例;
目录单例模式有很多种,这里我们先列举下:

  • 饿汉模式
  • 懒汉模式-线程不安全
  • 懒汉模式-线程安全
  • 懒汉模式-线程不是很安全
  • 懒汉模式-双重检查
  • 静态内部类
  • 枚举
正文 1. 饿汉模式(不推荐)
饿汉模式的核心就是第一次加载类的时候,进行数据的初始化;
而且这个数据不可被修改(final);
后续只能读,不能写。
这样一来,就保证了数据的准确性;
下面我们看下示例
package pattern.singleton; // 饿汉模式(不推荐),因为占内存 public class HungryDemo { private static final HungryDemo hungryDemo = new HungryDemo(); private HungryDemo() { }public static HungryDemo getInstance(){ return hungryDemo; }public static void main(String[] args) { HungryDemo hungryDemo1 = HungryDemo.getInstance(); HungryDemo hungryDemo2 = HungryDemo.getInstance(); System.out.println(hungryDemo1); System.out.println(hungryDemo2); System.out.println(hungryDemo1 == hungryDemo2); } }

从主程序中可以看到,不管获取多少次实例,都是同一个。
2. 懒汉模式-线程不安全(不推荐)
懒汉模式,就是类初始化时不加载数据,等到需要的时候才加载;
下面看示例:
package pattern.singleton; // 懒汉模式-线程不安全(不推荐) public class LazyDemo1 {private static LazyDemo1 lazyDemo; private LazyDemo1(){}public static LazyDemo1 getInstance(){ if(lazyDemo == null) lazyDemo = new LazyDemo1(); return lazyDemo; }public static void main(String[] args) { LazyDemo1 l1 = LazyDemo1.getInstance(); LazyDemo1 l2 = LazyDemo1.getInstance(); System.out.println(l1); System.out.println(l2); System.out.println(l1 == l2); } }

这样做的好处就是节省资源,只在需要的时候才加载;
但是会导致一个问题,就是线程的安全性;
比如两个线程同时获取,有可能获取到不同的实例;
3. 懒汉模式-线程安全(不推荐)
上面的懒汉模式,最大的缺点就是线程不安全;
所以我们可以升级一下,通过加锁来解决,如下所示
package pattern.singleton; // 懒汉模式-线程安全(不推荐) public class LazyDemo2 {private static LazyDemo2 lazyDemo; private LazyDemo2(){}// 给方法加锁,线程安全了,但是效率低 public static synchronized LazyDemo2 getInstance(){ if(lazyDemo == null) lazyDemo = new LazyDemo2(); return lazyDemo; }public static void main(String[] args) { LazyDemo2 l1 = LazyDemo2.getInstance(); LazyDemo2 l2 = LazyDemo2.getInstance(); System.out.println(l1); System.out.println(l2); System.out.println(l1 == l2); } }

这样一来,不管多少个线程去获取实例,都只会获取到同一个;
但是缺点也很明显,就是效率低;
比如现在已经遗弃的vector类,就是通过给方法上锁,来解决安全问题
4. 懒汉模式-线程不是很安全(不推荐)
这一次,我们又升级了上面的懒汉模式,把方法锁改为代码块锁,减小了锁的范围;
package pattern.singleton; import java.lang.management.ThreadInfo; // 懒汉模式-线程不安全(不推荐) // 解释:虽然加了代码同步块,但是还是存在线程不安全的情况public class LazyDemo3 {private static LazyDemo3 lazyDemo; private static int count = 0; private LazyDemo3(){}public static LazyDemo3 getInstance(){ if(lazyDemo == null){ // 1. 所有的线程会先执行下面的打印,然后第一个线程先获得锁,其他线程依次排队等待解锁 System.out.println(Thread.currentThread().getName()); synchronized (LazyDemo3.class){ try { System.out.println(Thread.currentThread().getName()+"等待中"); // 2. 当第一个进来的线程在这里休眠时,其他外面的线程是获取不到锁的,就会一直等待 Thread.sleep(1000); lazyDemo = new LazyDemo3(); System.out.println(Thread.currentThread().getName()+"等待结束"); // 3. 此时第一个线程释放锁,第二个线程因为已经通过了if(lazyDemo == null)的判断 // 所以会直接获取锁,然后重复刚才的步骤2,这样就会导致实例 lazyDemo 被创建多次 } catch (InterruptedException e) { e.printStackTrace(); } } } return lazyDemo; }public static void main(String[] args) { for (int i = 0; i < 2; i++) { new Thread(new Runnable() { @Override public void run() { LazyDemo3 l1 = LazyDemo3.getInstance(); System.out.println(l1); } }).start(); } } } /** * 下面是输出 * * Thread-0 * Thread-0等待中 * Thread-1 // 此时Thread-1已经通过了if()校验 * Thread-0等待结束 // Thread-0 释放锁 * Thread-1等待中 // Thread-1 获取锁 * pattern.singleton.LazyDemo3@568acee4 // 这是 Thread-0 创建的单例 * Thread-1等待结束 // Thread-1 释放锁 * pattern.singleton.LazyDemo3@3f580216 // 这是 Thread-1 创建的单例,此时就有了两个单例,就出问题了 * * */

通过例子可以看到,这两个线程交替执行去获取实例,虽然效率有所提高,但是结果却创建了两个实例,因小失大
所以这种方式也不推荐
#yyds干货盘点#设计模式之单例模式

文章图片

5. 懒汉模式-双重检查(推荐)
前面的几种懒汉模式,都是各有各的不足;
所以这里来个大招,将上面的不足都解决掉;
也就是双重检查模式。
package pattern.singleton; // 懒汉模式-双重检查(推荐) public class LazyDemo4 {// 保证可见性,即在多线程时,一个线程修改了这个变量,则其他线程立马就可以看到变化 private static volatile LazyDemo4 lazyDemo; private LazyDemo4(){}public static LazyDemo4 getInstance(){ if(lazyDemo == null) // 加同步代码块,保证当前只有一个线程在修改 lazyDemo synchronized (LazyDemo4.class){ // 加双重检查,其他后面进来的线程,如果看到 lazyDemo 已经创建了,则不再创建,直接返回 if(lazyDemo == null) lazyDemo = new LazyDemo4(); } return lazyDemo; }public static void main(String[] args) { LazyDemo4 l1 = LazyDemo4.getInstance(); LazyDemo4 l2 = LazyDemo4.getInstance(); System.out.println(l1); System.out.println(l2); System.out.println(l1 == l2); } }

可以看到,这里在获取到锁之后,又加了一个null判断,这样就可以保证在创建实例之前,确保实例真的是null
6. 静态内部类(推荐)
这个就比较简单了,不需要加锁,也不需要考虑null判断,直接将实例封装到内部类中,再用final修饰为不可变;
从而保证了这个实例的唯一性;
这个其实就是结合了前面的 饿汉模式 和 懒汉模式-双重检查。
package pattern.singleton; // 静态内部类(推荐) public class StaticInnerDemo {private StaticInnerDemo(){}; // 静态内部类 // 1. 当 StaticInnerDemo 加载时,下面的 InnerInstace 并没有加载 // 2. 当 调用getInstance()时,下面的静态内部类才会加载,且只会加载一次(因为final常量) private static class InnerInstance{ private static final StaticInnerDemo staticInnerDemo = new StaticInnerDemo(); }public static StaticInnerDemo getInstance(){ return InnerInstance.staticInnerDemo; }public static void main(String[] args) { StaticInnerDemo staticInnerDemo1 = getInstance(); StaticInnerDemo staticInnerDemo2 = getInstance(); System.out.println(staticInnerDemo1); System.out.println(staticInnerDemo2); System.out.println(staticInnerDemo1 == staticInnerDemo2); } }

7. 枚举(推荐)
最后来个压轴的,通过枚举来实现单例模式;
这个可以说是极简主义风格,自带单例效果;
因为不需要过多的修饰,只是单纯的定义一个枚举,然后创建一个实例,后面程序直接用这个实例就可以了。
package pattern.singleton; // 枚举(推荐) public enumEnumDemo { INSTANCE; public static void main(String[] args) { EnumDemo instance1 = EnumDemo.INSTANCE; EnumDemo instance2 = EnumDemo.INSTANCE; System.out.println(instance1); System.out.println(instance2); System.out.println(instance1 == instance2); } }

总结关于单例模式的实现方式,首推的就是枚举,其次是懒汉模式-双重检查,最后是静态内部类

    推荐阅读