《设计模式》——1 单例模式

22 篇文章 0 订阅
4 篇文章 0 订阅

《设计模式》,emmm~老朋友了鸭,一个在本科阶段学习的课程,平常就用到其中几个而已,现在研究生期间面临找实习了,还是再全面地复习一下吧。为了加深理解,本文用Python和Java两种语言进行实现。

  • 设计模式总体包括3大类、2小类,3大类是:
    创建型,共五种:工厂方法、抽象工厂、单例、建造者、原型。
    结构型,共七种:适配器、装饰器、代理、外观、桥接、组合、享元。
    行为型,共十一种:策略、模板方法、观察者、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者、中介者、解释器。
    2小类是:并发型模式、线程池模式。

  • 设计模式之间的关系:
    [外链图片转存失败(img-ZQlNtEhc-1563595227024)(http://dl.iteye.com/upload/attachment/0083/1179/57a92d42-4d84-3aa9-a8b9-63a0b02c2c36.jpg)]

1 单例模式

1.1 模式介绍

(1)含义

一句话:某个类只能有一个实例。

(2)好处

一句话:节省开销,节省内存,防止业务错乱。
(1) 节省开销:大型对象的创建比较复杂,需要很大的系统开销。
(2) 节省内存:没必要的对象,创建太多是在浪费内存。

举例:某个服务器程序的配置信息存放在一个文件中,客户端通过一个 AppConfig 的类来读取配置文件的信息。如果在程序运行期间,有很多地方都需要使用配置文件的内容,也就是说,很多地方都需要创建 AppConfig 对象的实例,这就导致系统中存在多个 AppConfig 的实例对象,而这样会严重浪费内存资源,尤其是在配置文件内容很多的情况下。事实上,类似 AppConfig 这样的类,我们希望在程序运行期间只存在一个实例对象。

(3) 防止业务错乱。

举例:有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例模式,才能保证核心交易服务器独立控制整个流程。

1.2 Python实现

  • 单线程。
# -*- coding: utf-8 -*-

class Singleton(object):
    def __init__(self):
#        print("4:",self)
        pass
    
    def __new__(cls, *args):
#        print("1:",cls.__name__)
        if not hasattr(Singleton, "_instance"): # 反射
            Singleton._instance = object.__new__(cls)
#        print("2:",Singleton._instance)
#        print("3:__new__完成")
        return Singleton._instance

obj1 = Singleton()
obj2 = Singleton()

print(obj1)
print(obj2)

#该程序执行过程讲解:(可以打开程序中的注释,观察执行顺序,执行顺序为:1,2,3,4。)
#类Singleton创建实例时,先将Singleton本身作为参数传递进__new__(),作为其第一个参数cls。
#__new__()将类型cls(即Singleton) 作为参数传递给父类的__new__(),父类的__new__()创建完cls的实例后,返回,然后赋值给Singleton._instance。

#__new__()只是创建了实例,分配了内存,但是还没有初始化(即实例Singleton._instance的属性还没有值)。
#Singleton.__new__()创建完实例后,python自动调用__init__()方法,将属性的值传递给__init__()。在__init__中将*args赋值给实例_instance的属性。

输出:

<__main__.Singleton object at 0x00000190F9661FD0>
<__main__.Singleton object at 0x00000190F9661FD0>
  • 多线程安全:加锁
import threading
class Singleton(object):
    def __init__(self):
        pass
    
    def __new__(cls, *args):
        mylock = threading.RLock()
        mylock.acquire()
        if not hasattr(Singleton, "_instance"): # 反射
            Singleton._instance = object.__new__(cls)
        mylock.release()
        return Singleton._instance
  • 多线程安全兼顾效率:双重检查锁——两个null检查
import threading
class Singleton(object):
    def __init__(self):
        pass
    
    def __new__(cls, *args):
        if not hasattr(Singleton,"_instance"):
            mylock = threading.RLock()
            mylock.acquire()
            if not hasattr(Singleton, "_instance"): # 反射
                Singleton._instance = object.__new__(cls)
            mylock.release()
            
        return Singleton._instance

1.3 Java实现

参考网址:你真的会写单例模式吗?

  1. 单线程:synchronized。
public class Singleton {
	private static volatile Singleton singleton = null;
	
	private static Singleton getSingleton() {
		//同步代码块
		synchronized (Singleton.class) {//参数是同步监视器,线程开始执行代码块之前,必须先获得对同步监视器的锁定。
			//同步监视器的目的是阻止两个线程对同一个共享资源进行并发访问。推荐使用可能被并发访问的共享资源充当同步监视器。
			if(singleton == null) singleton = new Singleton();
		}
		return singleton;
	}
}

缺点:多个线程访问,可能重复创建对象。
2. 多线程安全:加锁。synchronized + volatile。

public class Singleton {
	private static volatile Singleton singleton = null;
	//volatile关键字保证singleton对所有线程的可见性,可见性指在一个线程中对该变量的修改会马上由工作内存(Work Memory)写回主内存(Main Memory),所以会马上反应在其它线程的读取操作中。
	
	private static Singleton getSingleton() {
		//同步代码块
		synchronized (Singleton.class) {//参数是同步监视器,线程开始执行代码块之前,必须先获得对同步监视器的锁定。
			//同步监视器的目的是阻止两个线程对同一个共享资源进行并发访问。推荐使用可能被并发访问的共享资源充当同步监视器。
			if(singleton == null) singleton = new Singleton();
		}
		return singleton;
	}
}

缺点:
效率低下,无法实际应用。因为每次调用getSingleton()方法,都必须在synchronized这里进行排队,而真正遇到需要new的情况是非常少的,绝大多数都是可以并行的读操作。
3. 线程安全兼顾效率

public class Singleton {

	private static volatile Singleton singleton = null;
	
	private static Singleton getSingleton() {
		if(singleton == null) {
			//同步代码块
			synchronized (Singleton.class) {//参数是同步监视器,线程开始执行代码块之前,必须先获得对同步监视器的锁定。
				//同步监视器的目的是阻止两个线程对同一个共享资源进行并发访问。推荐使用可能被并发访问的共享资源充当同步监视器。
				if(singleton == null) singleton = new Singleton();
			}
		}
		return singleton;
	}
}

这种写法被称为“双重检查锁”,顾名思义,就是在getSingleton()方法中,进行两次null检查。看似多此一举,但实际上却极大提升了并发度,进而提升了性能。为什么可以提高并发度呢?就像上文说的,在单例中new的情况非常少,绝大多数都是可以并行的读操作。因此在加锁前多进行一次null检查就可以减少绝大多数的加锁操作,执行效率提高的目的也就达到了。

其实上述单例模式的实现都还有缺点,所以参考你真的会写单例模式吗?,这篇文章写得真心不错。

参考网址

[1] 常见设计模式
[2] 大话设计模式

第二次学习

三种方法都可以。
第一种较繁琐、第三种使用的较少。
第二、三种最安全。
初始化时,第一种可以传参数,第二种不可以传参数。

1 双重检查锁

package com.singleton;
//单例模式建议使用双重检查锁或静态内部类的方法实现。
/**
 * 可行解法1:双重检查锁。(推荐)
 * 加同步锁前后,两次判断实例是否存在。
 *注意:
 *1)静态实例:需要的时候创建。
 *2)私有构造方法:防止别人创建
 *3)两次null判断:提高效率
 *4)votatile限制:防止JVM乱序优化
 *4)同步代码块:线程安全
 * 缺点:实现复杂,容易出错
 */
public class Singleton1 {
	public volatile static Singleton1 singleton= null; // 静态,需要的时候创建
	//volatile的特点:可见性、有序性。有序性禁止对指令重排序。
	private Singleton1(){} // 防止别人自己创建
	public static Singleton1 getSingleton() {
		if(singleton == null){ // 不为null时就不用加锁了。判断后再锁,提高效率
			synchronized (Singleton1.class) {// 线程安全:因为同步块中的Singleton()方法是singleton对象的实例方法,所以给该实例加锁。并且,此时singleton实例可能还未创建,是一个空指针。synchronized(expr)中,expr表达式的值必须是某个对象的引用
				if(singleton == null) {//为什么又判断一次?一个线程在第一次判断null进入了,但是在还没进入同步块之前,另一个线程进入了第一个null,进入了同步块,创建了一个实例。然后第一个线程才进入同步块。
					singleton = new Singleton1();
					//包含三步:JVM乱序优化,容易出错
//			          1.在堆内存开辟内存空间。
//			          2.在堆内存中实例化SingleTon里面的各个参数。
//			          3.把对象指向堆内存空间。
				}
			}
		}
		return singleton;
	}
	public static void main(String[] args) {
		Singleton1 base1 = Singleton1.getSingleton();
		System.out.println(base1);
		Singleton1 base2 = Singleton1.getSingleton();
		System.out.println(base2);
	}
}

2 静态内部类

package com.singleton;
/*
 * 静态内部类(推荐)
 * 注意:
 * 1)私有构造方法:防止别人创建
 * 2)内部类:外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化INSTANCE,故而不占内存。即当SingleTon第一次被加载时,并不需要去加载SingleTonHoler,只有当getInstance()方法第一次被调用时,才会去初始化INSTANCE,第一次调用getInstance()方法会导致虚拟机加载SingleTonHoler类,这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。
 *缺点:初始化时,无法传参
 */
public class Singleton2 {
	private Singleton2(){}
	
	private static class SingletonHolder{
		public static Singleton2 singleton = new Singleton2();
	}

	public static Singleton2 getSingleton() {
		return SingletonHolder.singleton;
	}
	public static void main(String[] args) {
		Singleton2 singleton = Singleton2.getSingleton();
		System.out.println(singleton);
	}
}

3 枚举

package com.singleton;
/*其他的方法都有下面两个缺点:
 * 1)需要额外的工作来实现序列化,否则每次反序列化一个序列化的对象时都会创建一个新的实例。
 * 2)可以使用反射强行调用私有构造器(如果要避免这种情况,可以修改构造器,让它在创建第二个实例的时候抛异常)。
   *    而枚举类很好的解决了这两个问题,使用枚举除了线程安全和防止反射调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象。因此,《Effective Java》作者推荐使用的方法。不过,在实际工作中,很少看见有人这么写。
 */

enum Singleton{
	INSTANCE;
	
	private SingletonClass instance;
	
	private Singleton() {
		// TODO Auto-generated constructor stub
		this.instance = new SingletonClass();
	}
	
	public SingletonClass getInstance(){
		return this.instance;
	}
}

public class SingletonClass{
	public static SingletonClass getSingleton() {
		return Singleton.INSTANCE.getInstance();
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张之海

若有帮助,客官打赏一分吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值