单例模式--独一无二的对象

1.单例模式的定义

单例模式(Singleton Pattern):用来创建独一无二的,只能有一个实例的对象的入场券。

在我们进行开发的时候,有些对象我们只需要一个,比如:配置文件,工具类,线程池、缓存、日志对象等。如果创造出多个实例,就会导致许多问题,比如占用资源过多,不一致的结果等。使用单例模式就能保证在程序中需要的实例只有一个。

单例模式的类型:懒汉模式、饿汉模式。

 

2.单例模式的类图

下面是单例模式的类图:

我们将创建一个单例对象类 - Singleton。单例对象(Singleton)类的构造函数是私有的,并且具有自身的静态实例。

Singleton类提供了一个静态方法来获取其静态实例到外部世界。SingletonTest或示例类将使用Singleton类提供的静态方法来获取Singleton对象。

3.单例模式--饿汉式

饿汉式是在创建自身的静态实例的时候就直接实例化单例对象,然后在提供的静态方法中直接返回这个实例对象。

 1 /**
 2  * 单例模式(饿汉模式)
 3  * @author admin
 4  *
 5  */
 6 public class Singleton {
 7     //1.将构造方法私有化,不允许外部直接创建对象
 8     private Singleton(){
 9         }
10     
11     //2.创建类的唯一实例,使用private,static修饰
12     private static Singleton instance = new Singleton();
13     
14     //3.提供一个用于获取实例的方法,使用public static 修饰
15     public static Singleton getInstance(){
16         return instance;
17     }
18 }    

4.单例模式-懒汉式

懒汉式在创建自身的静态实例的时候不直接实例化单例对象,只有在外部调用获取单例对象的静态方法的时候才会判断单例对象是否已经存在,如果 不存在则创建一个并返回,如果已经存在则直接返回。

 1 /**
 2  * 单例模式(懒汉模式)
 3  * @author admin
 4  */
 5 public class Singleton2 {
 6     //1.将构造方法私有化,不允许外边直接创建对象
 7     private Singleton2(){
 8     }
 9     //2.声明类的唯一实例,使用private,static修饰,但是此处不实例化
10     private static Singleton2 instance;
11     //3.提供一个用于获取实例的方法,使用public static修饰
12     public static Singleton2 getInstance(){
13         if (instance == null) {
14             instance = new Singleton2();
15         }
16         return instance;
17     }
18 }

 

 

5.测试

 1 /**
 2  * 单例模式的测试类
 3  * @author admin
 4  *
 5  */
 6 public class SingletonTest {
 7     public static void main(String[] args) {
 8         //饿汉模式
 9         Singleton s1 = Singleton.getInstance();
10         Singleton s2 = Singleton.getInstance();
11         
12         if (s1 == s2) {
13             System.out.println("s1和s2是同一个对象");
14         }else {
15             System.out.println("s1和s2不是同一个对象");
16         }
17         
18         //懒汉模式
19         Singleton2 s3 = Singleton2.getInstance();
20         Singleton2 s4 = Singleton2.getInstance();
21         if (s3 == s4) {
22             System.out.println("s3和s4是同一个对象");
23         }else {
24             System.out.println("s3和s4不是同一个对象");
25         }
26     }
27 }

 

测试结果:

从测试结果可以看出来,获取的对象是同一个对象,也就是说,返回的是单例对象。

6.饿汉式和懒汉式的区别

  1. 饿汉式是在单例类加载的时候就初始化单例对象,所以在加载类的时候速度比较慢,但是在运行时的速度比较快,同时是线程安全的(线程安全问题下面再详细讲解)。
  2. 懒汉式在单例类加载的时候只定义单例对象,不进行初始化,所以加载类的时候速度比较快,但是在运行时需要进行单例对象的初始化,所以在运行时速度比较慢,而且是线程不安全的。

7.单例模式在多线程中的问题

上面的代码在普通的应用程序中没有任何任何问题,但是在多线程中使用的时候就会发现,返回的单例对象并不是唯一的,而且多个不同的单例对象,这就说明在多线程中,单例模式产生的单例对象并不是"唯一"的。要解决这个问题,有3种办法(其中两种方法都是针对懒汉式的),这3种方法在解决多线程问题的同时也有自己的缺点:

  • 第一种方法:使用"急切"的创建实例,而不用延迟实例化的做法。如果程序总是创建并使用单例实例,或者在创建和运行时方面的负担不太繁重,那就可以使用急切(eagerly)创建此单例实例。其实这就是饿汉式单例模式。利用这个做饭,我们依赖JVM在加载这个类是马上创建此唯一的单例实例。JVM保证在任何线程访问单例静态变量之前,一定先创建此单例实例。
  • 第二种方法(此方法是针对懒汉式单例模式):把静态类中的静态方法(上面代码中的getInstance())方法编程同步(synchronized)方法,多线程问题几乎就可以轻易的解决了。它的缺点是降低了性能,而且可能比这还更严重一些,通过单例模式的定义我们知道,单例对象只有在该方法第一次执行时,才真正需要同步,在以后的调用中,该单例对象已经存在了,所以并不需要再次进行实例化。也就是说,当设置好instance这个变量后,就不再需要同步这个方法了,之后的每次调用这个方法,同步都是一种累赘。而且,同步一个方法可能造成程序执行效率下降100倍,因此,如果在频繁运行的地方调用该方法,那么就得好好考虑这个问题了。当然,如果getInstance()方法的性能对应用程序不是很关键,那么可以忽略同步带来的问题。
  • 第三种方法(此方法也是针对懒汉式单例模式):用"双重检查加锁",在getInstance()方法中减少使用同步。利用双重检查加锁(double-checked locking),首先检查是否单例实例是否已经创建了,如果尚未创建,"才"进行同步。这样一来,只有第一次会同步,这正是我们想要的。将上面懒汉式的代码改成如下:
     1 /**
     2  * 单例模式(懒汉模式)
     3  * @author admin
     4  */
     5 public class Singleton2 {
     6     //1.将构造方法私有化,不允许外边直接创建对象
     7     private Singleton2(){
     8         
     9     }
    10     //2.声明类的唯一实例,使用private,static修饰,但是此处不实例化
    11     private volatile static Singleton2 instance;
    12     //3.提供一个用于获取实例的方法,使用public static修饰
    13     public static Singleton2 getInstance(){//检查实例,如果不存在就进入同步区块
    14         if (instance == null) {
    15             synchronized (Singleton2.class){//注意,只有第一次才彻底执行这里的代码
    16                 if (instance == null) {//进入区块后,再检查一次,如果还是null,才创建实例
    17                     instance = new Singleton2();
    18                 }
    19             }
    20         }
    21         return instance;
    22     }
    23 }

     volatile关键字确保:当单例变量被初始化成Singleton实例时,多个线程正确地处理单例变量。如果性能问题是关注的重点,那么这个方法可以大大地减少getInstance()的时间消耗。但是该方法也有其缺点,那就是双重检查加锁不适用于1.4及更早版本的Java。在1.4及其更早的版本中,许多JVM对于volatile关键字的实现会导致双重检查加锁的实效。如果不能使用Java5以上的版本,而必须使用旧版本,那么该方法就无法解决多线程的问题。

好了单例模式的叙述到此就结束了,如果有什么讲解的不正确的地方,欢迎大家多多指教!

文章部分内容引用自如下地址:http://www.yiibai.com/design_pattern/singleton_pattern.html

 

posted @ 2017-06-17 19:54  gouyadong  阅读(243)  评论(0编辑  收藏  举报