首页 > 代码库 > 设计模式——单例模式

设计模式——单例模式

技术分享

1、前言

1-1、 概述

       设计模式 = 某类特定问题的解决方案,那么单例模式是解决什么问题的解决方案呢?

  定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

  含义:单例  =  一个实例

  解决的问题:在任何时间内只有一个类实例存在的模式

  解决方法:保证一个类只有一个实例化对象,并提供一个全局访问入口

  本质:控制实例的数量

  注意:要合理的使用单例,避免单例成为瓶颈

  英文:Singleton

    类型:创建类模式

1-2、问题引入

模拟网站访问数量统计功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package com.designmode.singleton;
 
/**
 * 网站计数器
 */
class WebCounter {
    private int count = 0;
 
    public int getCount() {
        return count;
    }
    public void setCount(int count) {
        this.count = count;
    }
}
 
/**
 * 用户访问
 */
class Visitor{
    public WebCounter webCounter;
    public Visitor(WebCounter mwebCounter){
        webCounter = mwebCounter;
    }
    //访问
    public void visit(){
        webCounter.setCount(webCounter.getCount()+1);;
    }
}
/**
 * 模拟用户访问网站
 */
public class SingleTest{
    public static void main(String[] args){
        WebCounter webCounter1 = new WebCounter();
        WebCounter webCounter2 = new WebCounter();
        Visitor visitor1 = new Visitor(webCounter1);
        Visitor visitor2 = new Visitor(webCounter2);
 
        System.out.println("是不是同一个网站?");
        if(webCounter1.equals(webCounter2)){
            System.out.println("是");
        }else {
            System.out.println("不是");
        }
        //visitor1访问该网站
        visitor1.visit();
        System.out.println("访问量:" + webCounter1.getCount());
        //visitor2访问该网站
        visitor2.visit();
        System.out.println("访问量:" + webCounter2.getCount());
    }
}

结果:

1
2
3
4
是不是同一个网站?
不是
访问量:1
访问量:1

从结果看,两个人访问的不是一个网站实例,其实我们要实现的逻辑是,访问同一个网站,计算访问量,这显然是不符合我们想要的

 

2、介绍

 2-1、分析引入的问题

  冲突:从上面的结果可以看出,网站计数器类操作的明显不是同一个实例

  目标:所有访问者操作同一个网站计数器类

  单例模式就是为了解决这类问题的解决方案:实现一个类只有一个实例化对象,并提供一个全局访问入口

 

 2-2、解决引入的问题

解决:改造一下网站计数器类的实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package com.designmode.singleton;
 
/**
 * 网站计数器
 */
class WebCounter {
    private int count = 0;
    
    private static WebCounter instance  = new  WebCounter();
    private WebCounter() {
    }
    public static WebCounter getInstance() {
       return instance;
    }
     
  public int getCount() {
    return count;
  }
  public void setCount(int count) {
    this.count = count;
  }
}
/**
 * 用户访问
 */
class Visitor{
    public WebCounter webCounter;
    public Visitor(WebCounter mwebCounter){
        webCounter = mwebCounter;
    }
    //访问
    public void visit(){
        webCounter.setCount(webCounter.getCount()+1);;
    }
}
/**
 * 模拟用户访问网站
 */
public class SingleTest{
    public static void main(String[] args){
        WebCounter webCounter1 = WebCounter.getInstance();
        WebCounter webCounter2 = WebCounter.getInstance();
        Visitor visitor1 = new Visitor(webCounter1);
        Visitor visitor2 = new Visitor(webCounter2);
         
        System.out.println("是不是同一个网站?");
        if(webCounter1.equals(webCounter2)){
            System.out.println("是");
        }else {
            System.out.println("不是");
        }
        //visitor1访问该网站
        visitor1.visit();
        System.out.println("访问量:" + webCounter1.getCount());
        //visitor2访问该网站
        visitor2.visit();
        System.out.println("访问量:" + webCounter2.getCount());
    }
}

再来看一下结果:

1
2
3
4
是不是同一个网站?
访问量:1
访问量:2

这次是对的!!!

 

 2-3、实现原理

引入单例模式:一般实现方式

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Singleton {
    //1. 创建私有变量 instance(用以记录 Singleton 的唯一实例)
    //2. 内部进行实例化
    private static Singleton instance  = new  Singleton();
    //3. 把类的构造方法私有化,不让外部调用构造方法实例化
    private Singleton() {
    }
    //4. 定义公有方法提供该类的全局唯一访问点
    //5. 外部通过调用getInstance()方法来返回唯一的实例
    public static Singleton getInstance() {
        return instance;
    }
}

 

2-4、优点、缺点

优点: 
  1.在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就防止其它对象对自己的实例化,确保所有的对象都访问一个实例 
  2.单例模式具有一定的伸缩性,类自己来控制实例化进程,类在实例化进程上有相应的伸缩性
  3.提供了对唯一实例的访问入口
  4.由于在系统内存中只存在一个对象,因此可以节约系统资源,当需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能
  5.允许可变数目的实例(可以根据实际情况需要,在单例模式的基础上扩展做出双例模式、多例模式)
  6.避免对共享资源的多重占用
缺点: 
  1.不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态
  2.由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。 
  3.单例类的职责过重,在一定程度上违背了“单一职责原则” 
  4.滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失

 

3、实现

3-1、饿汉模式、懒汉模式

饿汉模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.designmode;
 
/**
 * 饿汉模式(最简单的形式)
 */
public class Singleton {
    private static  Singleton instance  = new  Singleton();
 
    private Singleton() {
    }
 
    public static Singleton getInstance() {
        return instance;
    }
}

应用场景:

  要求直接在应用启动时加载并初始化
  单例对象要求初始化速度非常快

 

懒汉模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.designmode;
 
/**
 * 懒汉模式(最简单的形式)
 */
public class Singleton {
    private static  Singleton instance  = null;
 
    private Singleton() {
    }
 
    public static Singleton newInstance() {
        if(instance == null){
            instance = new Singleton();
        }
        return instance;
    }
}

 应用场景:

  单例初始化的操作耗时比较长(可以相应缩短应用启动时间)
  单例只是在某个特定场景的情况下才会被使用,即按需延迟加载单例

 

对比:

  饿汉式:自动进行单例的初始化
  懒汉式:有需要的时候才手动调用getInstance()进行单例的初始化操作

3-2、多线程下的实现

在多线程的情况下:

  “饿汉式单例模式”:适用,因为JVM只会加载一次单例类
  “懒汉式单例模式”:不适用,因为“懒汉式”在创建单例时是线程不安全的,多个线程可能会并发调用 getInstance 方法从而出现重复创建单例对象的问题

下面有几个解决方案:

方案1:同步锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成instance被多次实例化
package com.designmode;
 
public class Singleton {
    private static  Singleton instance  = null;
 
    private Singleton() {
    }
 
    public static  Singleton getInstance() {
        synchronized (Singleton.class){
           if(instance == null){
              instance = new Singleton();
           }
        }
      return instance;
   }
}

  

方案2:双重校验锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//在同步锁的基础上( synchronized (Singleton.class) 外)添加了一层if,这是为了在Instance已经实例化后下次进入不必执行 synchronized (Singleton.class) 获取对象锁,从而提高性能
package com.designmode;
 
public class Singleton {
    private static  Singleton instance  = null;
 
    private Singleton() {
    }
 
    public static  Singleton getInstance() {
        if(instance == null){
            synchronized (Singleton.class){
                if(instance == null){
                    instance = new Singleton();
                }
            }
        }
        return instance;
   }
}

  

方案3:静态内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//在JVM进行类加载的时候会保证数据是同步的,我们采用内部类实现:在内部类里面去创建对象实例
//只要应用中不使用内部类 JVM 就不会去加载这个单例类,也就不会创建单例对象,从而实现“懒汉式”的延迟加载和线程安全。
package com.designmode;
 
public class Singleton {
    //在装载该内部类时才会去创建单例对象
    private static class Singleton2{
        private static Singleton instance  = new Singleton();
    }
     
    private Singleton() {
    }
 
    public static Singleton getInstance() {
        return Singleton2.instance;
    }
}

  

方案4:枚举类型

1
2
3
4
5
6
7
8
9
10
11
12
13
//最简洁、易用的单例实现方式,(《Effective Java》推荐)
package com.designmode;
 
public enum Singleton{
    //定义一个枚举的元素,它就是Singleton的一个实例
    INSTANCE;
    private Singleton() {
       
    }
    public void doSomething(){ 
         
    
}

调用方式:

1
Singleton.INSTANCE.doSomething();

  

4、总结

      设计模式 = 某类特定问题的解决方案,那么单例模式是解决什么问题的解决方案呢?

  含义:单例  =  一个实例

  解决的问题:在任何时间内只有一个类实例存在的模式

  解决方法:保证一个类只有一个实例化对象,并提供一个全局访问入口

  本质:控制实例的数量

  注意:要合理的使用单例,避免单例成为瓶颈

 

 

PS:源码地址   https://github.com/JsonShare/DesignPattern/tree/master

 

PS:原文地址 http://www.cnblogs.com/JsonShare/p/7093947.html

设计模式——单例模式