设计模式Design Pattern(2)--单例模式

单例顾名思义就是一个实例。类只有唯一一个实例,并提供给全局使用。解决了全局使用的类频繁地创建与销毁带了的消耗。

单例模式常用简单,但细究却又不简单,且往下看。

单例模式又可以分为

(1)懒汉式:需要使用实例时,才创建实例

(2)饿汉式:类加载时,就创建静态实例。

上代码

1、饿汉式,线程安全

 1 /**
 2  * 饿汉式--线程安全
 3  * 优点:没有加锁,执行效率会提高。
 4  * 缺点:类加载时就初始化,浪费内存。
 5  */
 6 public class UserHunger_Safe {
 7     /**
 8      * 对象实例
 9      */
10     private static UserHunger_Safe instance = new UserHunger_Safe();
11 
12     /**
13      * 私有构造函数
14      */
15     private UserHunger_Safe() {
16     }
17 
18     /**
19      * 对外提供公共获取实例方法(线程安全)
20      *
21      * @return
22      */
23     public static UserHunger_Safe getInstance() {
24         return instance;
25     }
26 }
UserHunger_Safe.java

2、懒汉式,线程不安全

 1 /**
 2  * 懒汉式单例模式
 3  * 线程不安全
 4  */
 5 public class UserLazy_Unsafe {
 6 
 7     /**
 8      * 私有构造方法
 9      */
10     private UserLazy_Unsafe() {
11     }
12 
13     /**
14      * 对象实例
15      */
16     private static UserLazy_Unsafe instance;
17 
18     /**
19      * 对外提供公共获取实例方法(线程不安全)
20      *
21      * @return
22      */
23     public static UserLazy_Unsafe getInstance() {
24         if (instance == null) {
25             instance = new UserLazy_Unsafe();
26         }
27         return instance;
28     }
29 }
UserLazy_Unsafe.java

3、懒汉式--方法加锁synchronized,线程安全 

 1 /**
 2  * 懒汉式单例模式
 3  * 优点:第一次调用才初始化,避免内存浪费。
 4  * 缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
 5  */
 6 public class UserLazy_Safe {
 7     /**
 8      * 私有构造方法
 9      */
10     private UserLazy_Safe() {
11     }
12 
13     /**
14      * 对象实例
15      */
16     private static UserLazy_Safe instance;
17 
18     /**
19      * 对外提供公共获取实例方法(线程安全)
20      *
21      * @return
22      */
23     public static synchronized  UserLazy_Safe getInstance() {
24         if (instance == null) {
25             instance = new UserLazy_Safe();
26         }
27         return instance;
28     }
29 }
UserLazy_Safe.java

方法加锁,则每次调用都需要锁定,严重影响性能,这不是想要的。所以优化下得出第4中方案

4、双重校验锁(DCL,即 double-checked locking)

 1 /**
 2  * 饿汉式+双重校验锁
 3  */
 4 public class UserDCL_Safe {
 5     /**
 6      * 对象实例
 7      */
 8     private static UserDCL_Safe instance;
 9 
10     /**
11      * 私有构造函数
12      */
13     private UserDCL_Safe() {
14     }
15 
16     /**
17      * 对外提供公共获取实例方法(线程安全)
18      *
19      * @return
20      */
21     public static synchronized UserDCL_Safe getInstance() {
22         if (instance == null) {
23             //双重校验锁
24             synchronized (UserDCL_Safe.class) {
25                 if (instance == null) {
26                     instance = new UserDCL_Safe();
27                 }
28             }
29         }
30         return instance;
31     }
32 }
UserDCL_Safe.java

如此一来,即便是多线程也能保证安全。如果只考虑静态域还可以使用内部静态类

5、静态内部类

 1 /**
 2  * 登记式/静态内部类
 3  * 线程安全
 4  * 这种方式能达到双检锁方式一样的功效,但实现更简单。
 5  * 这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
 6  */
 7 public class UserStaticInnerClass_Safe {
 8     private static class SingletonHolder{
 9         private static final UserStaticInnerClass_Safe INSTANCE=new UserStaticInnerClass_Safe();//实例
10     }
11 
12     //私有构造函数
13     private UserStaticInnerClass_Safe(){}
14     public  static final UserStaticInnerClass_Safe getInstance(){
15         return SingletonHolder.INSTANCE;
16     }
17 }
UserStaticInnerClass_Safe.java

至此,单例模式是不是绝对安全了?答案当然不是,在反射攻击下,外部依旧能做到非单例,不信,读者可以通过反射方式获得以上1,3,4,5例子的实例,就会发现依旧可以得到不同的实例。那有什么好的解决方案呢?

这个Effective Java 作者 Josh Bloch 大神给出了解决方案--使用枚举

6、枚举,最佳实现方式

 1 /**
 2  * 枚举方式实现单例模式
 3  */
 4 public enum UserEnum {
 5     INSTANCE;
 6 
 7     /**
 8      * 姓名
 9      */
10     private String name;
11 
12     /**
13      * 年龄
14      */
15     private int age;
16 
17     /**
18      * 手机号码
19      */
20     private String telephone;
21 
22     public String getName() {
23         return name;
24     }
25 
26     public void setName(String name) {
27         this.name = name;
28     }
29 
30     public int getAge() {
31         return age;
32     }
33 
34     public void setAge(int age) {
35         this.age = age;
36     }
37 
38     public String getTelephone() {
39         return telephone;
40     }
41 
42     public void setTelephone(String telephone) {
43         this.telephone = telephone;
44     }
45 
46     @Override
47     public String toString() {
48         return "SingletonPattern_EnumType{" +
49                 "name='" + name + '\'' +
50                 ", age=" + age +
51                 ", telephone='" + telephone + '\'' +
52                 '}';
53     }
54 }
UserEnum.java

如此一来既保证了线程安全,也保证了反序列化安全

测试

 1 import java.lang.reflect.Constructor;
 2 
 3 public class SigletonPattern {
 4     public static void main(String[] args) throws Exception {
 5         //懒汉式单例--线程不安全
 6         UserLazy_Unsafe ul1 = UserLazy_Unsafe.getInstance();
 7         UserLazy_Unsafe ul2 = UserLazy_Unsafe.getInstance();
 8         System.out.println(ul1 + "\n" + ul2);
 9         System.out.println("===========懒汉式单例--线程不安全 End==========");
10 
11         //懒汉式单例--线程安全.获取实例方法添加synchronized,加锁会影响效率。
12         UserLazy_Safe uls1 = UserLazy_Safe.getInstance();
13         UserLazy_Safe uls2 = UserLazy_Safe.getInstance();
14         System.out.println(uls1 + "\n" + uls2);
15         System.out.println("===========懒汉式单例--线程安全 End==========");
16 
17         //懒汉式单例--登记式/静态内部类--线程安全。
18         UserStaticInnerClass_Safe usic = UserStaticInnerClass_Safe.getInstance();
19         UserStaticInnerClass_Safe usic2 = UserStaticInnerClass_Safe.getInstance();
20         System.out.println(usic + "\n" + usic2);
21         System.out.println("===========懒汉式单例--登记式/静态内部类--线程安全 End==========");
22 
23         UserEnum user = UserEnum.INSTANCE;
24         //UserEnum user = new UserEnum();
25         user.setName("科技无国界");
26         user.setAge(0);
27         user.setTelephone("16895965423");
28 
29         UserEnum levon = UserEnum.INSTANCE;
30         //UserEnum levon = new UserEnum();
31         levon.setName("联想--科技无国界");
32         levon.setAge(10);
33         levon.setTelephone("26895965423");
34 
35         System.out.println(user);
36         System.out.println(levon);
37 
38         //通过反射获得对象
39         Constructor<UserEnum> constructor = UserEnum.class.getDeclaredConstructor();
40         constructor.setAccessible(true);
41         UserEnum sp = constructor.newInstance();
42         System.out.println(sp);
43     }
44 }
main方法
 1 UserLazy_Unsafe@1b6d3586
 2 UserLazy_Unsafe@1b6d3586
 3 ===========懒汉式单例--线程不安全 End==========
 4 UserLazy_Safe@4554617c
 5 UserLazy_Safe@4554617c
 6 ===========懒汉式单例--线程安全 End==========
 7 UserStaticInnerClass_Safe@74a14482
 8 UserStaticInnerClass_Safe@74a14482
 9 ===========懒汉式单例--登记式/静态内部类--线程安全 End==========
10 SingletonPattern_EnumType{name='联想--科技无国界', age=10, telephone='26895965423'}
11 SingletonPattern_EnumType{name='联想--科技无国界', age=10, telephone='26895965423'}
12 Exception in thread "main" java.lang.NoSuchMethodException: UserEnum.<init>()
13     at java.lang.Class.getConstructor0(Class.java:3082)
14     at java.lang.Class.getDeclaredConstructor(Class.java:2178)
15     at SigletonPattern.main(SigletonPattern.java:39)
输出结果

 示例源码:https://github.com/LF20160912/pattern

posted @ 2019-05-31 14:47  天晴修屋顶  阅读(142)  评论(0编辑  收藏  举报