单例模式

软件发布|下载排行|最新软件

当前位置:首页IT学院IT技术

单例模式

showMeTheCodes   2020-02-28 我要评论

这篇文章中我会用8种写法来对单例模式进行优化,以达到最完美的效果

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

单例模式

什么是单例模式?

简单的说就是只能new出来一个实例

第一种写法

饿汉式:

  优点:简单实用

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

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 }

总结

单例模式在现在编写代码时使用较少,spring帮我们保证了仅仅初始化一个实例对象,但是我们也应该理解单例模式的实现原理

Copyright 2022 版权所有 软件发布 访问手机版

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 联系我们