第五式 单例模式

单例模式

什么是单例模式

  单例模式:确保一个类只有一个实例,并提供一个全局访问点。

  我们把某个类设计成自己管理的一个单独实例,同时也避免其他类再自行产生实例。同时提供该实例的全局访问点,当你需要实例时,向类查询,会返回单个实例。

如何实现

  平时我们需要对象时,都是new一个出来。这次单例设计模式,通过new的方式,每次出来的对象都不是同一个了,不符合单例模式设计原则。new对象是通过

类自身的公开的构造方法,我们可以通过将构造方式私有不让别人调用,然后提供一个静态方法让需要的人来获取该实例对象。根据该实例对象创建时间,可以分为

懒汉式和饿汉式。

  饿汉式:

1 //饿汉式
2 public class Single {
3     private static Single single = new Single();
4     private Single(){}
5     public static Single getInstance(){
6         return single;
7     }
8 }

  懒汉式:

 1 //懒汉式
 2 public class Single {
 3     private static Single single;
 4     private Single(){}
 5     public static Single getInstance(){
 6         if(single == null){
 7             single = new Single();
 8         }
 9         return single;
10     }
11 }

  利弊:饿汉式在程序运行时就创建好了单例对象,这个对象可能会一直用不上,但一开始就被创建了占用资源,造成浪费。懒汉式是有人要用到这个单例对象时才把它创建出来,达到了

资源利用的最合理化,但是他不是线程安全的。看下面的代码就知道了,私有的构造方法被调用了两次,也就是说没有实现输出单例。

 1 public class Client {
 2     public static void main(String[] args){
 3         for(int i=0;i<2;i++){
 4             //创建两个线程,去获取单例
 5             new Thread(){
 6                 public void run(){
 7                     Singleton.getInstance();
 8                 }
 9             }.start();
10         }
11     }
12 }
13 
14 class Singleton {
15     private static Singleton singleton;
16     private Singleton(){
17         System.out.println("我被创建了!");
18         try {
19             //模拟复杂对象创建时,耗费的时间
20             Thread.sleep(1000);
21         } catch (InterruptedException e) {
22             e.printStackTrace();
23         }
24     }
25     public static Singleton getInstance(){
26         if(singleton == null){
27             singleton = new Singleton();
28         }
29         return singleton;
30     }
31 }
32 
33 运行结果:
34 我被创建了!
35 我被创建了!

  如果我们将getInstance方法变成同步方法,就可以避免多线程问题。像下面这样

1     public static synchronized Singleton getInstance(){
2         if(singleton == null){
3             singleton = new Singleton();
4         }
5         return singleton;
6     }
7 
8  运行结果:
9  我被创建了!

  通过增加synchronized关键字到getInstance()方法中,我们迫使每个线程在进入这个方法之前,要先等别的线程离开该方法,这样就没有两个线程同时进入这个方法了。虽然同步解决了线程安全这个问题,但是同步会降低性能,这又是另外一个问题。实际上只有第一次执行getInstance()方法时,才真正需要同步,一旦实例被创建完毕之后,就不需要同步了。优化一下代码

 1 public static Singleton getInstance(){
 2         if(singleton == null){
 3             synchronized (Singleton.class){
 4                 if(singleton == null){
 5                     singleton = new Singleton();
 6                 }
 7             }
 8         }
 9         return singleton;
10     }
11 
12 运行结果:
13 我被创建了!

  这方法叫做“双重检查加锁”,在getInstance()中减少使用同步。

  静态内部类实现单例模式,可以兼顾线程安全和懒加载。

 1 //这种方式,线程安全,调用效率高,并且实现了延迟加载
 2 class Singleton {
 3     private static class SingletonClassInstance{
 4         private static final Singleton singleton = new Singleton();
 5     }
 6     private Singleton(){
 7         System.out.println("我被创建了!");
 8         try {
 9             //模拟复杂对象创建时,耗费的时间
10             Thread.sleep(1000);
11         } catch (InterruptedException e) {
12             e.printStackTrace();
13         }
14     }
15     public static Singleton getInstance(){
16         return SingletonClassInstance.singleton;
17     }
18 }
   运行结果:
  我被创建了!

   要点:外部类没有static属性,不会像饿汉式那样立即加载对象;只有调用getInstance才会加载静态内部类,而加载类是天然的线程安全的,singleton是static final类型,保证了内存中只有一个实例存在;兼备了并发高效调用和延迟加载的优势。

应用场景

  1、Windows的Task Manager(任务管理器)就是典型的单例模式

  2、网站的计数器也是单例,不然不好同步

  3、应用程序的日志应用,一般都使用单例模式,因为共享的日志文件一直处于打卡状态,只能一个实例去操作,否则不好追加

  4、Spring中,每个Bean默认就是单例,这样可以方便被Spring容器管理

  5、spring MVC框架中,控制器对象也是单例

猜你喜欢

转载自www.cnblogs.com/bwyhhx2018/p/10753247.html
今日推荐