01设计模式——单例模式

Posted hilter

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了01设计模式——单例模式相关的知识,希望对你有一定的参考价值。

前言:以下总结来自龙哥---左潇龙博客。

总结的很到位,附上博客链接:http://www.cnblogs.com/zuoxiaolong/p/pattern2.html

目的:尽可能的节约内存空间,减少无谓的GC消耗,并且使应用可以正常运作。

如何判断单例:这些类,在应用中如果有两个或者两个以上的实例会引起错误,又或者我换句话说,就是这些类,在整个应用中,同一时刻,有且只能有一种状态。

 1 /**
 2  * 简单单例模式 
 3  * 目的,是为了尽可能的节约内存空间,减少无谓的GC消耗,并且使应用可以正常运作。
 4  * 一般一个类能否做成单例,最容易区别的地方就在于,这些类,在应用中如果有两个或者两个以上的实例会引起错误,又或者我换句话说,就是这些类,在整个应用中,同一时刻,有且只能有一种状态。
 5  * 1.静态实例,带有static关键字的属性在每一个类中都是唯一的。
 6  * 2.限制客户端随意创造实例,即私有化构造方法,此为保证单例的最重要的一步。
 7  * 3.给一个公共的获取实例的静态方法,注意,是静态的方法,因为这个方法是在我们未获取到实例的时候就要提供给客户端调用的,所以如果是非静态的话,那就变成一个矛盾体了,因为非静态的方法必须要拥有实例才可以调用。
 8  * 4.判断只有持有的静态实例为null时才调用构造方法创造一个实例,否则就直接返回。
 9  */
10 public class Singleton {
11 
12     // 一个静态的实例
13     private static Singleton singleton;
14 
15     // 私有化构造函数
16     private Singleton() {
17         super();
18     }
19 
20     // 给出一个公共的静态方法返回一个单一实例
21     public static Singleton getInstance() {
22         if (singleton == null) {
23             singleton = new Singleton();
24         }
25         return singleton;
26     }
27 
28     // 验证单例
29     public static void main(String[] args) {
30         Singleton instance1 = Singleton.getInstance();
31         Singleton instance2 = Singleton.getInstance();
32         System.out.println(instance1);
33         System.out.println(instance2);
34     }
35 }
 1 import java.util.Collections;
 2 import java.util.HashSet;
 3 import java.util.Set;
 4 import java.util.concurrent.ExecutorService;
 5 import java.util.concurrent.Executors;
 6 import java.util.concurrent.TimeUnit;
 7 
 8 /** 
 9  * 验证Singleton线程不安全
10  */
11 public class TestSingleton {
12 
13     private volatile boolean lock;
14 
15     public boolean isLock() {
16         return lock;
17     }
18 
19     public void setLock(boolean lock) {
20         this.lock = lock;
21     }
22 
23     /**
24      * 当并发访问的时候,第一个调用getInstance方法的线程A,在判断完singleton是null的时候,线程A就进入了if块准备创造实例,
25      * 但是同时另外一个线程B在线程A还未创造出实例之前,就又进行了singleton是否为null的判断,这时singleton依然为null,
26      * 所以线程B也会进入if块去创造实例,这时问题就出来了,有两个线程都进入了if块去创造实例,结果就造成单例模式并非单例。
27      * */
28     public static void main(String[] args) throws InterruptedException {
29         final Set<String> instanceSet = Collections.synchronizedSet(new HashSet<String>());
30         final TestSingleton lock = new TestSingleton();
31         lock.setLock(true);
32         ExecutorService executorService = Executors.newCachedThreadPool();
33         for (int i = 0; i < 100; i++) {
34             executorService.execute(new Runnable() {
35 
36                 public void run() {
37                     while (true) {
38                         if (!lock.isLock()) {
39                             Singleton singleton = Singleton.getInstance();
40                             instanceSet.add(singleton.toString());
41                             break;
42                         }
43                     }
44                 }
45             });
46         }
47         // main线程睡眠了两次
48         Thread.sleep(5000);// 第一次是为了给足够的时间让100个线程全部开启,
49         lock.setLock(false);
50         Thread.sleep(5000);// 第二个是将锁打开以后,保证所有的线程都已经调用了getInstance方法。
51         System.out.println("------并发情况下我们取到的实例------");
52         for (String instance : instanceSet) {
53             System.out.println(instance);
54         }
55         executorService.shutdown();
56         executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
57         System.out.println("程序运行结束!!!");
58     }
59 }
 1 /**
 2  * 就是将整个获取实例的方法同步,
 3  * 这样在一个线程访问这个方法时,其它所有的线程都要处于挂起等待状态,倒是避免了刚才同步访问创造出多个实例的危险,
 4  * 这样的设计实在是糟糕透了,这样会造成很多无谓的等待。
 5  */
 6 public class BadSynchronizedSingleton {
 7 
 8     // 一个静态的实例
 9     private static BadSynchronizedSingleton singleton;
10 
11     // 私有化构造函数
12     private BadSynchronizedSingleton() {
13         super();
14     }
15 
16     // 给出一个公共的静态方法返回一个单一实例
17     public static BadSynchronizedSingleton getInstance() {
18         if (singleton == null) {
19             singleton = new BadSynchronizedSingleton();
20         }
21         return singleton;
22     }
23 
24     // 验证单例
25     public static void main(String[] args) {
26         BadSynchronizedSingleton instance1 = BadSynchronizedSingleton.getInstance();
27         BadSynchronizedSingleton instance2 = BadSynchronizedSingleton.getInstance();
28         System.out.println(instance1);
29         System.out.println(instance2);
30     }
31 }
 1 /**
 2  * 双重加锁
 3  * JVM创建新的对象时,主要要经过三步。
 4  * 1.分配内存
 5  * 2.初始化构造器
 6  * 3.将对象指向分配的内存的地址
 7  * 这种顺序在上述双重加锁的方式是没有问题的,因为这种情况下JVM是完成了整个对象的构造才将内存的地址交给了对象。
 8  * 但是如果2和3步骤是相反的(2和3可能是相反的是因为JVM会针对字节码进行调优,而其中的一项调优便是调整指令的执行顺序),就会出现问题了。
 9  * 因为这时将会先将内存地址赋给对象,针对上述的双重加锁,就是说先将分配好的内存地址指给synchronizedSingleton,
10  * 然后再进行初始化构造器,这时候后面的线程去请求getInstance方法时,会认为synchronizedSingleton对象已经实例化了,直接返回一个引用。
11  * 如果在初始化构造器之前,这个线程使用了synchronizedSingleton,就会产生莫名的错误。
12  */
13 public class SynchronizedSingleton {
14 
15     // 一个静态的实例
16     private static SynchronizedSingleton singleton;
17 
18     // 私有化构造函数
19     private SynchronizedSingleton() {
20         super();
21     }
22 
23     // 给出一个公共的静态方法返回一个单一实例
24     /**
25      *  假设我们去掉同步块中的是否为null的判断,
26      *  有这样一种情况,假设A线程和B线程都在同步块外面判断了synchronizedSingleton为null,
27      *  结果A线程首先获得了线程锁,进入了同步块,
28      *  然后A线程会创造一个实例,此时synchronizedSingleton已经被赋予了实例,
29      *  A线程退出同步块,直接返回了第一个创造的实例,
30      *  此时B线程获得线程锁,也进入同步块,此时A线程其实已经创造好了实例,B线程正常情况应该直接返回的,
31      *  但是因为同步块里没有判断是否为null,直接就是一条创建实例的语句,
32      *  所以B线程也会创造一个实例返回,此时就造成创造了多个实例的情况。
33      */
34     public static SynchronizedSingleton getInstance() {
35         if (singleton == null) {
36             synchronized (SynchronizedSingleton.class) {
37                 if (singleton == null) {
38                     singleton = new SynchronizedSingleton();
39                 }
40             }
41         }
42         return singleton;
43     }
44 
45     // 验证单例
46     public static void main(String[] args) {
47         SynchronizedSingleton instance1 = SynchronizedSingleton.getInstance();
48         SynchronizedSingleton instance2 = SynchronizedSingleton.getInstance();
49         System.out.println(instance1);
50         System.out.println(instance2);
51     }
52 
53 }
 1 /** 
 2  * 比较标准的单例模式
 3  * 一个类的静态属性只会在第一次加载类时初始化,这是JVM帮我们保证的,所以我们无需担心并发访问的问题。
 4  * 所以在初始化进行一半的时候,别的线程是无法使用的,因为JVM会帮我们强行同步这个过程。
 5  * 另外由于静态变量只初始化一次,所以singleton仍然是单例的。 
 6  * 
 7  * 1.Singleton最多只有一个实例,在不考虑反射强行突破访问限制的情况下。
 8  * 2.保证了并发访问的情况下,不会发生由于并发而产生多个实例。
 9  * 3.保证了并发访问的情况下,不会由于初始化动作未完全完成而造成使用了尚未正确初始化的实例。
10  * 
11  */
12 public class Singleton {
13 
14     private Singleton() {
15         super();
16     }
17 
18     public static Singleton getInstance() {
19         return SingletonInstance.instance;
20     }
21 
22     private static class SingletonInstance {
23         static Singleton instance = new Singleton();
24     }
25 
26 }
 1 /**
 2  * 饿汉式
 3  * 这种方式最主要的缺点
 4  *     就是一旦我访问了Singleton的任何其他的静态域,就会造成实例的初始化,而事实是可能我们从始至终就没有使用这个实例,造成内存的浪费。
 5  *  不过在有些时候,直接初始化单例的实例也无伤大雅,对项目几乎没什么影响,比如我们在应用启动时就需要加载的配置文件等,就可以采取这种方式去保证单例。 
 6  */
 7 public class Singleton {
 8 
 9     // 一个静态的实例
10     private static Singleton singleton = new Singleton();
11 
12     // 私有化构造函数
13     private Singleton() {
14         super();
15     }
16 
17     // 给出一个公共的静态方法返回一个单一实例
18     public static Singleton getInstance() {
19         return singleton;
20     }
21 
22     // 验证单例
23     public static void main(String[] args) {
24         Singleton instance1 = Singleton.getInstance();
25         Singleton instance2 = Singleton.getInstance();
26         System.out.println(instance1);
27         System.out.println(instance2);
28     }
29 
30 }

 

以上是关于01设计模式——单例模式的主要内容,如果未能解决你的问题,请参考以下文章

单例模式

[01]Go设计模式:单例模式(Singleton)

每日一题01.单例模式

2. 单例模式

JS单例模式

python中的单例设计模式