设计模式之单例模式(Singleton Pattern)
定义:单例模式是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象
解决问题:提供一个全局使用的类频繁地创建与销毁。保证一个类仅有一个实例,并提供一个访问它的全局访问点
单例模式的组成部分:
1.私有的构造方法
2.私有的当前对象作为对象的静态属性
3.公有的向外界提供类对象的静态方法
但凡一个类只能实例化一个对象的模式都叫做单例模式。
应用场景:
1.整个程序的运行中只允许有一个类的实例。
2.需要频繁实例化然后销毁的对象。
3.创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
4.方便资源相互通信的环境
策略模式中的角色:
1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。
2、避免对资源的多重占用(比如写文件操作)
单例模式的优点:
1、提供了对唯一实例的受控访问。
2、由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
3、允许可变数目的实例。
单例模式的缺点:
1、由于没有抽象层,因此单例类的扩展有很大的困难。
2、单例类的职责过重,在一定程度上违背了“单一职责原则”。
3、滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;
4、如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。
案例:代理模式之古代皇帝(过渡期的不考虑)
中国的历史上很少出现两个皇帝并存的时期,有但不多。在这个场景中,有皇帝,有大臣,大臣是天天要上朝参见皇帝的,今天参拜的皇帝应该和昨天、前天的一样
UML类图:
代码实现:
单例模式的实现方式包含很多种,包括了饿汉式,懒汉式,内部类式,静态内部类式和枚举类式。具体代码如下:
1、皇帝类,案例分几个方式实现单例模式,
一、饿汉式(静态变量)
优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。
package com.dzghyd.designmode.singleton;
public class EmperorInHunger {
private final static EmperorInHunger instance = new EmperorInHunger();
private EmperorInHunger(){}
public static EmperorInHunger getInstance(){
return instance;
}
public void respond(){
System.out.println("皇帝("+this.instance+"):众爱卿平身!");
}
}
二、饿汉式(静态代码块)
优点和缺点同上
package com.dzghyd.designmode.singleton.Emperor;
public class EmperorInHungerStatic {
private static EmperorInHungerStatic instance;
static{
instance= new EmperorInHungerStatic();
}
private EmperorInHungerStatic(){}
public static EmperorInHungerStatic getInstance(){
return instance;
}
public void respond(){
System.out.println("皇帝("+this.instance+"):众爱卿平身!");
}
}
三、懒汉式(方法锁)
缺点:效率低下,锁的位置不合理。
package com.dzghy.designmode.singleton.Emperor;
/**
* 单例模式一 :懒汉式 锁方法
*/
public class EmperorInLazyMethod {
private static EmperorInLazyMethod instance;
private EmperorInLazyMethod(){}
public static synchronized EmperorInLazyMethod getInstance(){
return instance;
}
public void respond(){
System.out.println("皇帝("+this.instance+"):众爱卿平身!");
}
}
四、双重检验锁
优点:线程安全;延迟加载;效率较高。
package com.dzghyd.designmode.singleton.Emperor;
/**
* 单例模式一 :双重检验锁模式
* 进行了两次if (singleton == null)检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,
* 判断if (singleton == null), 直接return实例化对象。
*/
public class EmperorInDoubleSync {
private static volatile EmperorInDoubleSync instance;//禁止指令重排序
private EmperorInDoubleSync(){}
public static EmperorInDoubleSync getInstance(){
if(instance == null){
synchronized(EmperorInDoubleSync.class){
if(instance == null){
instance = new EmperorInDoubleSync();
}
}
}
return instance;
}
public void respond(){
System.out.println("皇帝("+this.instance+"):众爱卿平身!");
}
}
五、内部类方式
优点:JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的,避免了线程不安全,延迟加载,效率高
package com.dzghyd.designmode.singleton.Emperor;
/**
* 单例模式一 :内部类方式
*/
public class EmperorInerClass {
private EmperorInerClass(){}
public static EmperorInerClass getInstance(){
return InnerClass.instance;
}
public static class InnerClass{
private final static EmperorInerClass instance = new EmperorInerClass();
}
public void respond(){
System.out.println("皇帝("+InnerClass.instance+"):众爱卿平身!");
}
}
六、枚举类方式
优点:这是实现单例模式的最佳方法。它更简洁,不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。
缺点:不是延迟加载的。
package com.dzghyd.designmode.singleton.Emperor;
/**
* 单例模式一 :枚举类方式
*/
public enum EmperorInEnem {
INSTANCE;
public static void respond(){
System.out.println("皇帝("+this.INSTANCE+"):众爱卿平身!");
}
}
然后就是大臣类,不管那是哪种方式,参拜的都是一个皇帝
package com.dzghyd.designmode.singleton;
import com.dzghyd.designmode.singleton.Emperor.EmperorInHunger;
public class Minister {
public static void main(String[] args) {
//第一天上朝
System.out.println("第一天上朝,百官参拜,吾皇万岁万岁万万岁!");
EmperorInHunger emperor1=EmperorInHunger.getInstance();
emperor1.respond(); //皇帝回应,打印对象地址
//第二天上朝
System.out.println("第二天上朝,百官参拜,吾皇万岁万岁万万岁!");
EmperorInHunger emperor2=EmperorInHunger.getInstance();
emperor2.respond();
//第三天上朝
System.out.println("第三天上朝,百官参拜,吾皇万岁万岁万万岁!");
EmperorInHunger emperor3=EmperorInHunger.getInstance();
emperor3.respond();
}
}
执行结果: