设计模式之单例模式

来源:互联网 发布:java 存在 编辑:程序博客网 时间:2024/06/06 08:35

前言

单例模式(Singleton Pattern)保证一个类仅有一个实例,并提供一个它的全局访问点。

一.单例模式简介

单例模式(Singleton Pattern)为了防止多次实例化一个对象,为该对象提供一个访问该实例的方法,让类自身负责保存它唯一的实例,这个类可以保证没有其他实例可以被创建。

二.单例模式多种用法分析

1.懒汉式–非线程安全单例模式

设计分析:需要一个私有的(private)空构造方法,防止外部类通过new创建实例。提供一个创建实例的方法getinstance()来创建实例,若实例已经被创建过,则直接返回原有实例。

线程安全问题:在单线程的程序中,用此方法可以实现单例,但在多线程编程中,当两个线程同时调用getinstance()方法时,因为两个线程的instance对象都为空,这两个线程可能都会执行new Singleton(),这两个线程可能各自创建一个instance对象,创建出多个instance对象即不是单例模式了,也就存在线程安全的问题了。

为什么称为懒汉:要在第一次引用实例的时候,才进行实例化,实现懒加载效果,节省内存资源,所以称为懒汉式。

代码如下:

package com.pattern.singleton.singleton;/** * 懒汉式-非线程安全式 *  * @author 葫芦娃 *  */public class Singleton {    // 单例对象    private static Singleton instance;    // 私有的构造函数,使用private修饰,防止外界通过new再次创建对象    private Singleton() {    }    // 提供一个外部访问该实例的方法    public static Singleton getinstance() {        // 只有当不存在的时候创建(线程不安全,没有加锁,可以被多个线程调用)        if (instance == null) {            instance = new Singleton();        }        // 若存在,直接返回该实例        return instance;    }}

2.懒汉式——线程安全单例模式

设计分析:与上面的不同之处在于,getinstance()方法添加了synchronized关键字修饰,即添加了方法同步,多线程会在进入此方法前排队依次执行,所以就解决了上面方式的线程安全问题,多线程编程中使用,也可以保证对象实现单例。

代码如下:

package com.pattern.singleton.singleton;/** * 懒汉式-线程安全式 * @author 葫芦娃 * */public class LazySingleton {    // 单例对象    private static LazySingleton instance;    // 私有的构造函数,使用private修饰,防止外界通过new再次创建对象    private LazySingleton() {    }    // 提供一个外部访问该实例的方法,并使用synchronized关键字同步    public static synchronized LazySingleton getinstance() {        // 只有当不存在的时候创建        if (instance == null) {            instance = new LazySingleton();        }        // 若存在,直接返回该实例        return instance;    }}

3.饿汉式–线程安全单例模式

设计分析: 在类初始化静态变量的时候,将instance实例化,提供私有的构造方法,防止通过new创建对象,提供getinstance()方法来创建实例。

线程安全问题: 因为类加载的时候,实例就已经初始化完成,就算有多个线程调用,也不会重复创建了,但缺点就是浪费内存资源。

为什么称为饿汉:在类加载的时候,初始化静态变量的时候就创建了该对象。就像一个饿汉,不管有用没用的,先吃到肚子里再说。

代码如下:

package com.pattern.singleton.singleton;/** * 饿汉式单例模式(线程安全) *  * @author 葫芦娃 * */public class HungrySingleton {    // 静态单例对象,程序运行直接初始化    private static HungrySingleton instance =new HungrySingleton();    // 私有的构造函数,使用private修饰,防止外界通过new再次创建对象    private HungrySingleton() {    }    // 提供一个外部访问该实例的方法    public static HungrySingleton getinstance() {        //在静态变量中已经初始化,直接返回该实例        return instance;    }}

4.双检锁/双重校验锁式——线程安全单例模式

设计分析: 提供一种即不耗费内存资源(懒加载),还能保证线程安全的方式创建单例模式。

双重校验:
1. 第一重校验:if (instance == null)防止实例已经初始化,还要让多线程每次进行synchronized排队,提高效率。
2. 第二重校验:if (instance == null)防止多线程并发时,保证只有一个线程在进行实例化操作,即实现单例,还能保证线程安全。

代码如下:

package com.pattern.singleton.singleton;/** * 双检锁/双重校验锁式的单例(线程安全) *  * @author 葫芦娃 * */public class DoubleCheckLockingSingleton {    // volatile修饰的单例对象    private static volatile DoubleCheckLockingSingleton instance;    // 私有的构造函数,使用private修饰,防止外界通过new再次创建对象    private DoubleCheckLockingSingleton() {    }    // 提供一个外部访问该实例的方法    public static DoubleCheckLockingSingleton getinstance() {        //第一重校验:防止实例已经初始化,还要每次进行加锁,提高效率        if (instance == null) {            // 线程同步,当多个线程的instance为null,都可以通过第一重校验            synchronized (DoubleCheckLockingSingleton.class) {                // //第二重校验:防止多个线程的访问,只允许有一个进入,其他线程排队等第一个线程访问结束再进入,保证instance对象的单例                if (instance == null) {                    instance = new DoubleCheckLockingSingleton();                }            }        }        return instance;    }}

5.客户端:

在客户端中,对比实例是单例,即创建的都是同一个实例。

代码如下:

package com.pattern.singleton.client;import com.pattern.singleton.singleton.DoubleCheckLockingSingleton;import com.pattern.singleton.singleton.HungrySingleton;import com.pattern.singleton.singleton.LazySingleton;import com.pattern.singleton.singleton.Singleton;public class Client {    public static void main(String[] args) {        // 通过new Singleton()来创建实例已经被禁用了,只能通过下面提供的方式        Singleton singleton1 = Singleton.getinstance();        Singleton singleton2 = Singleton.getinstance();        if (singleton1 == singleton2) {            System.out.println("懒汉式非线程安全:singleton1和singleton2是相同实例");        } else {            System.out.println("懒汉式非线程安全:singleton1和singleton2是不同实例");        }        // 线程安全的单单例模式        final LazySingleton lazySingleton4 = LazySingleton.getinstance();        // 饿汉式单例,保证了线程安全        final HungrySingleton hungrySingleton5 = HungrySingleton.getinstance();        // 双重校验创建线程安全的单例对象        final DoubleCheckLockingSingleton doubleCheckLockingSingleton7 = DoubleCheckLockingSingleton.getinstance();        new Thread(new Runnable() {            public void run() {                LazySingleton lazySingleton5 = LazySingleton.getinstance();                if (lazySingleton4 == lazySingleton5) {                    System.out.println("懒汉式线程安全:lazySingleton4和lazySingleton4是相同实例");                } else {                    System.out.println("懒汉式线程安全:lazySingleton4和lazySingleton4是不同实例");                }                HungrySingleton hungrySingleton6 = HungrySingleton.getinstance();                if (hungrySingleton5 == hungrySingleton6) {                    System.out.println("饿汉式线程安全:hungrySingleton5和hungrySingleton6是相同实例");                } else {                    System.out.println("饿汉式线程安全:hungrySingleton5和hungrySingleton6是不同实例");                }                DoubleCheckLockingSingleton doubleCheckLockingSingleton8 = DoubleCheckLockingSingleton.getinstance();                if (doubleCheckLockingSingleton7 == doubleCheckLockingSingleton8) {                    System.out.println("双重校验锁式线程安全:doubleCheckLockingSingleton7和doubleCheckLockingSingleton8是相同实例");                } else {                    System.out.println("双重校验锁式线程安全:doubleCheckLockingSingleton7和doubleCheckLockingSingleton8是不同实例");                }            }        }).start();    }}

6.运行结果展示

懒汉式非线程安全:singleton1和singleton2是相同实例懒汉式线程安全:lazySingleton4和lazySingleton4是相同实例饿汉式线程安全:hungrySingleton5和hungrySingleton6是相同实例双重校验锁式线程安全:doubleCheckLockingSingleton7和doubleCheckLockingSingleton8是相同实例

7.源码下载

本文示例代码下载地址:点击下载

三.总结:

1.单例模式的优点:

  • 只有一个实例,减少内存的开销。
  • 对资源不会多重的占用,比如避免了多个实例对一份问价读写。

2.单例模式的缺点:

  • 单一职责原则冲突,一个类应该只关注它本身,不应该关注外面的类如何实例化它。
  • 不能提供接口。
  • 不能够继承。
原创粉丝点击