1,单例模式

1.1,单例的理解

单例模式是一种常用的软件设计模式,其定义的单例对象的类只能允许一个实例存在;
该类负责创建自己的对象,同时确保只有一个对象被创建;
一般常用在工具类的实现或创建对象需要消耗资源的业务场景。

1.2,单例的特点

类构造器私有;
持有自己类的引用;
对外提供获取实例的静态方法。

1.3,常用单例模式

饿汉模式:好处:没有线程安全;坏处:浪费内存空间。好处:没有线程安全问题
// 坏处:浪费内存空间好处
好处:没有线程安全问题;坏处:浪费内存空间
懒汉模式:好处:没有内存空间的浪费;坏处:控制不好就是单例模式,比如多个线程执行时。
双重检查锁:既保证了线程安全(synchronized),又比直接上锁提高了执行效率(第一层校验),还节省了内存空间。
静态内部类:外呼类加载时不需要加载 静态内部类,不加载则不占用内存;当外部类调用 getInstance 方法时,才加载静态内部类(延迟加载),静态属性保证了全局唯一,静态变量初始化保证了线程安全,所以这里的方法没有加 synchronized 关键字(JVM保证了一个类的 初始化在多线程下被同步加锁)。

2,饿汉模式

  1. public class SimpleSingleton {
  2. private static final SimpleSingleton INSTANCE = new SimpleSingleton();
  3. private SimpleSingleton(){}
  4. public static SimpleSingleton getInstance(){
  5. return INSTANCE;
  6. }
  7. public static void main(String[] args) {
  8. // 单例模式-饿汉模式
  9. // 实例在初始化的时候就已经建好了,不管是否使用到,都已经实例化好了。
  10. // 好处:没有线程安全问题
  11. // 坏处:浪费内存空间
  12. System.out.println(SimpleSingleton.getInstance().hashCode());
  13. System.out.println(SimpleSingleton.getInstance().hashCode());
  14. // 2084435065
  15. // 2084435065
  16. }
  17. }

3,懒汉模式

  1. public class SimpleSingleton2 {
  2. // 懒汉模式
  3. private static SimpleSingleton2 INSTANCE;
  4. private SimpleSingleton2(){}
  5. public static SimpleSingleton2 getInstance(){
  6. if(null == INSTANCE){
  7. return new SimpleSingleton2();
  8. }
  9. return INSTANCE;
  10. }
  11. public static void main(String[] args) {
  12. // 懒汉模式
  13. // 就是实例在用到的时候才创建,用的时候才去检查有无实例,有则返回,反之新建。
  14. // 优点:没有内存空间的浪费
  15. // 缺点:控制不好就不是单例模式,比如多个线程执行时
  16. System.out.println(SimpleSingleton2.getInstance().hashCode());
  17. System.out.println(SimpleSingleton2.getInstance().hashCode());
  18. // 2084435065
  19. // 1896277646
  20. }
  21. }

优化处理:

  1. public class SimpleSingleton3 {
  2. private static SimpleSingleton3 INSTANCE;
  3. private SimpleSingleton3(){}
  4. public synchronized static SimpleSingleton3 getInstance(){
  5. if(null == INSTANCE){
  6. return new SimpleSingleton3();
  7. }
  8. return INSTANCE;
  9. }
  10. public static void main(String[] args) {
  11. // 改进懒汉模式,加 synchronized 关键字
  12. // 使用 synchronized 关键字会消耗性能,我们应该判断INSTANCE为空时才加锁,
  13. // 而不为空不应该加锁,需要直接返回。这就需要使用双重检查锁。
  14. System.out.println(SimpleSingleton3.getInstance().hashCode());
  15. System.out.println(SimpleSingleton3.getInstance().hashCode());
  16. // 2084435065
  17. // 1896277646
  18. }
  19. }

4,双重检查锁

  1. public class SimpleSingleton4 {
  2. private static SimpleSingleton4 INSTANCE;
  3. private SimpleSingleton4(){}
  4. // 双重检查锁
  5. public static SimpleSingleton4 getInstance(){
  6. if(null == INSTANCE){
  7. synchronized (SimpleSingleton4.class){
  8. if(null == INSTANCE){
  9. INSTANCE = new SimpleSingleton4();
  10. }
  11. }
  12. }
  13. return INSTANCE;
  14. }
  15. public static void main(String[] args) {
  16. // 双重检查锁
  17. System.out.println(SimpleSingleton4.getInstance().hashCode());
  18. System.out.println(SimpleSingleton4.getInstance().hashCode());
  19. // 2084435065
  20. // 2084435065
  21. }
  22. }

优化处理-双重检查锁-volatile:

  1. public class SimpleSingleton5 {
  2. // JVM虚拟机可能会重排 getInstance() 方法,所以需要添加关键字 volatile
  3. // volatile 关键字可以保证多个线程的可见性,但不能保证原子性,同时它也能禁止指令重排。
  4. // 可见性:即表现为 多个线程可同时读取同一变量时,且数据同步,共享结果
  5. // 原子性:即表现为 该操作不可拆分,同一时刻只能有一个线程对它进行操作,不会被其他调度器所打断。
  6. private volatile static SimpleSingleton5 INSTANCE;
  7. private SimpleSingleton5(){}
  8. public static SimpleSingleton5 getInstance(){
  9. if(null == INSTANCE){
  10. synchronized(SimpleSingleton5.class){
  11. if(null == INSTANCE){
  12. INSTANCE = new SimpleSingleton5();
  13. }
  14. }
  15. }
  16. return INSTANCE;
  17. }
  18. public static void main(String[] args) {
  19. // 双重检查锁的机制 既保证了线程安全(synchronized),又比直接上锁提高了执行效率(第一层校验),还节省了内存空间。
  20. System.out.println(SimpleSingleton5.getInstance().hashCode());
  21. System.out.println(SimpleSingleton5.getInstance().hashCode());
  22. // 2084435065
  23. // 2084435065
  24. }
  25. }

5,静态内部类

  1. public class SimpleSingleton6{
  2. // 构造器私有
  3. private SimpleSingleton6(){}
  4. // 提供静态方法获取实例对象
  5. public static SimpleSingleton6 getInstance(){
  6. return Inner.INSTANCE;
  7. }
  8. // 保证单例对象,且静态变量保证唯一,线程安全
  9. // 只有外呼类调用 getInstance 方法时,静态内部类才被加载,初始化变量,不占用内存
  10. private static class Inner{
  11. private static final SimpleSingleton6 INSTANCE = new SimpleSingleton6();
  12. }
  13. public static void main(String[] args){
  14. System.out.println(SimpleSingleton6.getInstance().hashCode());
  15. System.out.println(SimpleSingleton6.getInstance().hashCode());
  16. // 2084435065
  17. // 2084435065
  18. }
  19. }

示例:

  1. public class SimpleSingleton6 {
  2. private SimpleSingleton6(){}
  3. public static SimpleSingleton6 getInstance(){
  4. return Inner.INSTANCE;
  5. }
  6. private static class Inner{
  7. private static final SimpleSingleton6 INSTANCE = new SimpleSingleton6();
  8. }
  9. public static void main(String[] args) {
  10. // 静态内部类
  11. // 在类中我们定义了一个静态的内部类 Inner,该类的 getInstance 方法返回的内部类 Inner 的实例 INSTANCE.
  12. // 只有第一次调用 getInstance 方法时,虚拟机才加载 Inner 类并初始化 INSTANCE,
  13. // 只有一个线程可以获得对象的初始化锁,其他线程无法进行初始化,保证对象的唯一性。
  14. System.out.println(SimpleSingleton6.getInstance().hashCode());
  15. System.out.println(SimpleSingleton6.getInstance().hashCode());
  16. // 2084435065
  17. // 2084435065
  18. // 静态内部类的特点:
  19. // 外部类加载时不需要加载 静态内部类,不被加载则不占用内存;
  20. // 当外部类调用 getInstance 方法时,才加载静态内部类[延迟加载],静态属性保证了全局唯一,
  21. // 静态变量初始化保证了线程安全,所以这里的方法没有加 synchronized 关键字(JVM保证了一个类的 初始化在多线程下被同步加锁)
  22. }
  23. }