zoukankan      html  css  js  c++  java
  • 单例模式的8种写法

    这篇文章中我会用8种写法来对单例模式进行优化

    但是说实话在平常我们进行代码编写的时候用不着那么完美

    第一种写法

    饿汉式:

      优点:简单实用

      缺点:不论该对象是否会被用到,都提前将对象实例化

    1.首先我们创建出一个静态的不可更改的变量Instance

    2.我们将该类的构造方法的权限设置为private,防止其他类new对象

    3.设置该对象的get方法

     1 /**
     2  * 饿汉式
     3  * 类加载到内存后,就实例化一个单例,JVM保证线程安全
     4  * (JVM保证每一个class只会露到内存一次,那么static变量在class露到内存之后马上进行初始化,所以static变量也保证初始化这一次)
     5  * 简单实用,推荐使用
     6  * 唯一缺点:不管用到与否,类加载时就完成实例化
     7  */
     8 public class Mgr01 {
     9     private static final Mgr01 Instance = new Mgr01();
    10 
    11     private Mgr01(){}
    12 
    13     public static Mgr01 getInstance(){return Instance;}
    14 
    15     public static void main(String[] args) {
    16         /**
    17          * 调用静态方法常用的两种方式
    18          *   1.new对象调用静态方法
    19          *   2.类名打点调用静态方法
    20          *   这里构造方法设为私有访问控制符,不能new对象
    21          */
    22         Mgr01 m1 = Mgr01.getInstance();
    23         Mgr01 m2 = Mgr01.getInstance();
    24         System.out.println(m1 == m2);
    25     }
    26 }

    第二种写法

    第二种写法与第一种写法几乎没有区别,只是使用了静态代码块进行对象的初始化
    但是该写法仍然没有解决类加载时初始化的问题
     1 /**
     2  * 与Mgr01意思相同
     3  */
     4 public class Mgr02 {
     5     //这里加上final没有初始化,但是在下面必须加static静态代码块进行初始化
     6     private static final Mgr02 Instance;
     7 
     8     static{
     9         Instance = new Mgr02();
    10     }
    11 
    12     private Mgr02(){}
    13 
    14     public static Mgr02 getInstance(){return Instance;}
    15 
    16     public static void main(String[] args) {
    17         Mgr02 m1 = Mgr02.getInstance();
    18         Mgr02 m2 = Mgr02.getInstance();
    19         System.out.println(m1 == m2);
    20     }
    21 }

    第三种写法

    懒汉式:

      优点:在需要的时候进行对象初始化,解决了以上两种写法的缺点

      缺点:带来了线程不安全的问题

    在进行判断是否存在Instance实例时,我们假设有现后两个线程,一号线程刚判断完发现没有实例,正准备进行new时,二号线程突然进入,判断结果同样为没有Instance实例,这时就会有两个线程先后执行new操作

     1 /**
     2  * 懒汉式
     3  * 达到了按需初始化的目的,但是带来了线程不安全的问题
     4  */
     5 public class Mgr03 {
     6     //这里不能加final,因为加上final就必须进行初始化new对象
     7     private static Mgr03 Instance;
     8 
     9     private Mgr03(){}
    10 
    11     public static Mgr03 getInstance(){
    12         if (Instance == null){
    13             //多线程同时打入的时候容易在这里产生误差
    14             Instance = new Mgr03();
    15         }
    16         return Instance;
    17     }
    18 
    19     public static void main(String[] args) {
    20         Mgr03 m1 = Mgr03.getInstance();
    21         Mgr03 m2 = Mgr03.getInstance();
    22         System.out.println(m1 == m2);
    23     }
    24 }

    第四种写法

    在getInstance方法上加锁来保证线程安全,但是如果每个线程在执行getInstance时都进行加锁操作,那么就会降低程序执行效率

     1 /**
     2  * 增加了线程的安全性,但是降低了程序执行效率
     3  */
     4 public class Mgr04 {
     5     private static Mgr04 Instance;
     6 
     7     private Mgr04(){}
     8 
     9     public static synchronized Mgr04 getInstance(){
    10         if (Instance == null){
    11             Instance = new Mgr04();
    12         }
    13         return Instance;
    14     }
    15     public static void main(String[] args) {
    16         Mgr04 m1 = Mgr04.getInstance();
    17         Mgr04 m2 = Mgr04.getInstance();
    18         System.out.println(m1 == m2);
    19     }
    20 }

    第五种写法

    试图通过同步代码块的方式在保证线程安全的前提下提高效率

    同步代码块只加在需要new实例的时候

    这样虽然提高了程序执行效率,但是显然是不能保证线程安全的

    假设有两个线程:线程一在if条件判断结束后发现没有实例,正在往下执行但是还没有进入同步代码块时,线程二进入也同样判断没有实例,线程二比线程一提前拿到锁new出来实例后释放锁,等到线程二释放锁之后线程一又执行方法new出来实例。这样就导致了线程的不安全性

     1 public class Mgr05 {
     2     private static Mgr05 Instance;
     3 
     4     private Mgr05(){}
     5 
     6     public static Mgr05 getInstance(){
     7         if (Instance == null){
     8             /**
     9              * 试图通过同步代码块的方式提高效率
    10              * 但是这里又很容易造成线程不安全问题
    11              */
    12             synchronized (Mgr05.class){
    13                 Instance = new Mgr05();
    14             }
    15         }
    16         return Instance;
    17     }
    18     public static void main(String[] args) {
    19         Mgr05 m1 = Mgr05.getInstance();
    20         Mgr05 m2 = Mgr05.getInstance();
    21         System.out.println(m1 == m2);
    22     }
    23 }

    第六种写法(比较完美的写法之一)

    双重if判断来保证只有一个实例对象

    这样即不会出现线程不安全问题,又保证了不会随着类加载而创建出来实例

     1 public class Mgr06 {
     2     //加volatile主要是为了防止指令重排
     3     private static volatile Mgr06 Instance;
     4 
     5     private Mgr06(){}
     6 
     7     public static Mgr06 getInstance(){
     8         //第一个进入方法的线程进行Instance实例是否存在的判断
     9         if (Instance == null){
    10             //同步代码块进行加锁
    11             synchronized (Mgr06.class){
    12                 //双重锁机制进行判断Instance对象是否被实例
    13                 if (Instance == null){
    14                     Instance = new Mgr06();
    15                 }
    16             }
    17         }
    18         return Instance;
    19     }
    20     public static void main(String[] args) {
    21         Mgr06 m1 = Mgr06.getInstance();
    22         Mgr06 m2 = Mgr06.getInstance();
    23         System.out.println(m1 == m2);
    24     }
    25 }

    第七种写法(比较完美的写法之一)

    使用静态内部类来保证只有一个实例

    静态内部类在加载类的时候是不会被加载的,而getInstance方法在执行返回Instance实例调用静态内部类时,静态内部类才会被加载,保证了在使用时才会创建实例

    JVM只会加载一次类,同样也只会加载一次静态内部类,这样就保证了线程安全,只会产生一个实例对象

     1 /**
     2  * 使用静态内部类保证单例,同时也保证线程安全
     3  * 线程安全是用过JVM机制来保证的
     4  * JVM只会加载一次Mgr07这个类,只会加载一次Mgr07Holder这个内部类
     5  *      这也就保证了只会生成一个Instance实例
     6  */
     7 public class Mgr07 {
     8     private Mgr07(){}
     9 
    10     /**
    11      * 使用静态内部类进行实现
    12      * 当Mgr07这个类被加载时,里面的内部类是不会被加载的,保证了类加载时不完成实例化
    13      * 当调用getInstance方法时内部类才会加载,也保证了只有一个实例
    14      */
    15     private static class Mgr07Holder{
    16         private static final Mgr07 Instance = new Mgr07();
    17     }
    18 
    19     public static Mgr07 getInstance(){
    20         return Mgr07Holder.Instance;
    21     }
    22 
    23     public static void main(String[] args) {
    24         Mgr07 m1 = Mgr07.getInstance();
    25         Mgr07 m2 = Mgr07.getInstance();
    26         System.out.println(m1 == m2);
    27     }
    28 }

    第八种写法(最完美的写法)

    枚举单例模式,枚举类中没有构造方法,保证了仅仅实例化一个对象

    只有在调用Instance实例的时候才会创建实例,保证了在使用时才会创建实例

     1 /**
     2  * 枚举单例模式:通过枚举进行单例模式的实例创建
     3  * 原因:枚举类中没有构造方法
     4  * 不仅可以解决线程同步,还可以防止反序列化
     5  */
     6 public enum Mgr08 {
     7 
     8     Instance;
     9 
    10     public static void main(String[] args) {
    11         Mgr08 m1 = Mgr08.Instance;
    12         Mgr08 m2 = Mgr08.Instance;
    13         System.out.println(m1 == m2);
    14     }
    15 }
  • 相关阅读:
    python 注释
    python元祖
    浅谈单片机应用程序架构----本质是定时调用
    原子哥的STM32视频,我发现他们都看不懂原子哥里面按键扫描程序
    指针函数与函数指针的区别
    基于不带字库的图形LCD模块汉字显示解决方案
    GB2312编码
    C语言可变参简介
    kEIL5环境下移置STM32库文件
    nodejs表单验证
  • 原文地址:https://www.cnblogs.com/lyc-code/p/12378454.html
Copyright © 2011-2022 走看看