一、什么是单例设计模式?
- 单例设计模式就是为了确保一个类只有一个实例存在,给外界提供一个公共的方法获取该类的唯一实例。
二、为什么要使用单例设计模式?
- 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级的对象而言,是非常可观的一笔系统开销。
- 由于new操作的次数减少,所以系统内存的使用评率也会降低,这将减少GC压力,缩短GC时STW。
三、如何实现单例设计模式?
-
1、饿汉式
// 饿汉式第一种写法 public class Mgr01 { private static final Mgr01 instance = new Mgr01(); // 私有构造 private Mgr01() { } public static Mgr01 getInstance() { return instance; } } // 饿汉式第二种写法 public class Mgr03 { private Mgr03() {} private static final Mgr03 instance; static { instance = new Mgr03(); } public static Mgr03 getInstance() { return instance; } }
- 优点
- 类加载的时候,类就已经实例化了。
- 缺点
- 但是有时候不需要类加载的时候就实例化。所以就有了懒汉式的写法。
- 优点
-
2、懒汉式
public class Mgr02 { private static Mgr02 instance; private Mgr02() {} public static Mgr02 getInstance() { if (instance == null) { instance = new Mgr02(); } return instance; } }
- 优点
- 什么时候需要,什么时候创建。
- 缺点
- 存在线程安全的问题:线程A和线程B都想调用
getInstance()
方法,线程A先拿到,执行到if
判断时,此时还没new
,这个时候线程B来了,然后直接new
出来了一个实例,再到线程A的时候,会继续执行new
对象的操作,就会产生两个实例。
- 存在线程安全的问题:线程A和线程B都想调用
- 优点
-
3、
synchronized
修饰方法的写法。-- 解决懒汉式的线程安全问题。public class Mgr04 { private Mgr04() {} private static Mgr04 instance; // 通过上锁的方式解决懒汉式线程安全问题。只能同时有一个线程访问以下代码。 public static synchronized Mgr04 getInstance(){ if(instance == null) { instance = new Mgr04(); } return instance; } }
- 优点
- 解决了懒汉式的线程安全问题。
- 缺点
- 多线程情况下,效率偏低。
- 优点
-
4、
synchronized
代码块的写法。public class Mgr05 { private Mgr05() { } private static Mgr05 instance; public static Mgr05 getInstance() { if (instance == null) { synchronized (Mgr05.class) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } instance = new Mgr05(); } } return instance; } // 测试 public static void main(String[] args) { for (int i = 0; i < 100; i++) { new Thread(()->{ System.out.println(Mgr05.getInstance().hashCode()); }).start(); } } }
- 优点
- 解决了性能偏低的问题。
- 缺点
- 带来了新的线程安全问题。当线程1执行到了if判断,然后线程2抢到cpu的执行权,去执行了,new了一个对象.此时线程1接着执行,会又new一个对象.
- 优点
-
5、DCL模式–double check lock双重检查锁
public class Mgr06 { private Mgr06() {} // 加volatile,是因为new 一个对象的时候,有三步,加了volatile防止这三步发生指令重排序. private static volatile Mgr06 instance; public static Mgr06 getInstance() { if (instance == null) { synchronized (Mgr06.class) { if (instance == null) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } instance = new Mgr06(); } } } return instance; } }
- 优点
- 解决了线程安全的问题和性能偏低的问题。
- 注意
- 必须用volatile修饰。
- 上锁前后都需要进行非空检查。
- 优点
-
6、静态内部类的方式
public class Mgr07 { private Mgr07() {} private static class Mgr07Holder { private static final Mgr07 INSTANCE = new Mgr07(); } public static Mgr07 getInstance() { return Mgr07Holder.INSTANCE; } }
- 特点
- 类加载的时候,对象不会立马初始化,因为内部类不会随着类加载而加载,相当于实现了懒加载。
- jvm来保证线程安全,只有一个实例。虚拟机加载一个class的时候,只加载一次,所以INSTANCE只会加载一次。
- 特点
-
7、枚举的写法
public enum Mgr08 { INSTANCE; }
- 特点
- 简单粗暴。可以防止反序列化的问题。
- Effective Java作者推荐的用法。
- 特点