单例模式(对象私有化)
目录
单例模式(Singleton Pattern)是Java中最简单的设计模式之⼀。这种类型的设计模式属于创建型模式,它提供了 ⼀种创建对象的最佳⽅式。这种模式涉及到⼀个单⼀的类,该类负责创建⾃⼰的对象,同时确保只有单个对象被创建。这个类提供了⼀种访问其唯⼀的对象的⽅式,可以直接访问,不需要实例化该类的对象。
注意:
1.单例类只能有⼀个实例。
2.单例类必须⾃⼰创建⾃⼰的唯⼀实例。
3.单例类必须给所有其他对象提供这⼀实例。
1.介绍
意图:保证⼀个类仅有⼀个实例,并提供⼀个访问它的全局访问点。
主要解决:⼀个全局使⽤的类频繁地创建与销毁。
何时使⽤:当你想控制实例数⽬,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数是私有的。
应⽤实例:
1.多个进程或线程同时操作⼀个⽂件,所以所有⽂件的处理必须通过唯⼀的实例进⾏。
2.⼀些设备管理器常常设计为单例模式,⽐如⼀个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同⼀个⽂件。
优点:
1.在内存⾥只有⼀个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。
2.避免对资源的多重占⽤(⽐如写⽂件操作)。
缺点:
没有接⼝,不能继承,与单⼀职责原则冲突,⼀个类应该只关⼼内部逻辑,⽽不关⼼外⾯怎么样来实例化。
使⽤场景:
1.要求⽣产唯⼀序列号。
2.WEB中的计数器,不⽤每次刷新都在数据库⾥加⼀次,⽤单例先缓存起来。
3.创建的⼀个对象需要消耗的资源过多,⽐如I/O与数据库的连接等。
2.实现
我们将创建⼀个SingleObject类。SingleObject类有它的私有构造⽅法和本身的⼀个静态实例。 SingleObject类提供了⼀个静态⽅法,供外界获取它的静态实例。
3 单例模式实现⽅式
3.1 饿汉式
提前将对象准备好,加载到内存中
3.1.1 饿汉式介绍
是否Lazy初始化:否 (提前准备好对象)
是否多线程安全:是 (饿汉式只有一个实例,没有多线程)
描述:这种⽅式⽐较常⽤,但容易产⽣垃圾对象。 (可能创建出来的对象一直没有用)
优点:没有加锁,执⾏效率会提⾼。
缺点:类加载时就初始化,浪费内存。
它基于classloader机制避免了多线程的同步问题,不过instance在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中⼤多数都是调⽤getInstance()⽅法, 但是也不能确定有其他的⽅式(或者其他的静态⽅法)导致类装载,这时候初始化instance显然没有达到lazy loading的效果。
3.1.2 饿汉式案例
1.创建⼀个Java项⽬。
2.创建⼀个HungrySingleton类。
package com.设计模式.饿汉式;
public class HungrySingleton {
//设置为静态,实现全局访问 //提前将对象准备好,加载到内存中
private static HungrySingleton instance = new HungrySingleton();
//构造方法私有化目的是为了在其他类里不能创建该对象
private HungrySingleton(){}
//提供静态方法来访问类中的私有成员
public static HungrySingleton getInstance(){
return instance;
}
public void showMessage(){
System.out.println("创建instence成功,showMessage执行了");
}
}
3.从HungrySingleton类获取唯⼀的对象。
package com.设计模式.饿汉式;
public class Test_HungrySingleton {
public static void main(String[] args) {
HungrySingleton hungrySingleton = HungrySingleton.getInstance();
hungrySingleton.showMessage();
}
}
4.执⾏程序,输出结果。
3.2 懒汉式
什么时候使用对象,什么时候创建对象
3.2.1 懒汉式线程不安全
3.2.1.1 懒汉式线程不安全介绍
是否Lazy初始化:是 (懒汉式是在对象真正需要被实例化的时候,才去创建对象)
是否多线程安全:否 (多线程同时进行到if时,可能创建多个instance)
描述:这种⽅式是最基本的实现⽅式,这种实现最⼤的问题就是不⽀持多线程。因为没有加锁synchronized,所以严格意义上它并不算单例模式。
这种⽅式lazy loading很明显,不要求线程安全,在多线程不能正常⼯作。
3.2.1.2 懒汉式线程不安全案例
1.创建⼀个LazySingleton类。
package com.设计模式.懒汉式.线程不安全;
/*
什么时候使用对象,什么时候创建对象 (懒加载)
*/
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton (){}
public static LazySingleton getInstance(){
if (instance == null){
instance = new LazySingleton();
}
return instance;
}
public void showMessage(){
System.out.println("创建instance成功,showMessage执行了");
}
}
2.从LazySingleton类获取唯⼀的对象。
package com.设计模式.懒汉式.线程不安全;
import com.设计模式.懒汉式.线程不安全.LazySingleton;
public class Test_LazySingleton {
public static void main(String[] args) {
LazySingleton lazySingleton = LazySingleton.getInstance();
lazySingleton.showMessage();
}
}
3.执⾏程序,输出结果。
3.2.2 懒汉式线程安全
3.2.2.1 懒汉式线程安全介绍
是否Lazy初始化:是
是否多线程安全:是 (加了锁)
描述:这种⽅式具备很好的lazy loading,能够在多线程中很好的⼯作,但是效率很低,99%情况下不需要同步。
优点:第⼀次调⽤才初始化,避免内存浪费。
缺点:必须加锁synchronized才能保证单例,但加锁会影响效率。
getInstance()的性能对应⽤程序不是很关键(该⽅法使⽤不太频繁)。
3.2.2.2 懒汉式线程安全案例
对原来类中的getInstance()方法⽤synchronized修饰。
package com.设计模式.懒汉式.线程安全;
/*
什么时候使用对象,什么时候创建对象
*/
public class LazySingleton {
private static LazySingleton instance;
private LazySingleton (){}
//加锁变成多线程安全
public static synchronized LazySingleton getInstance(){
if (instance == null){
instance = new LazySingleton();
}
return instance;
}
public void showMessage(){
System.out.println("创建instance成功,showMessage执行了");
}
}
3.3 双检锁/双重校验锁
处在懒汉式的基础上,比懒汉式更高效
3.3.1 双检锁/双重校验锁介绍
是否Lazy初始化:是
是否多线程安全:是
描述:双检锁/双重校验锁(DCL,即double-checked locking),这种⽅式采⽤双锁机制,安全且在多线程情况下能保持⾼性能。
getInstance()的性能对应⽤程序很关键。
3.3.2 双检锁/双重校验锁案例
volatile是⼀个特征修饰符(type specifier)。volatile的作⽤是作为指令关键字,在指令层面保证当前对象的值不会被误读或恶意修改,确保本条指令不会因编译器的优化⽽省略,且要求每次直接读值。简单地说就是防⽌编译器对代码进⾏优化。
1.创建⼀个DCLSingleton类。
package com.设计模式.双检锁;
public class DCLSingleton {
/*
volatile:
修饰类的属性的
在指令层面保证当前对象的值不会被误读或恶意修改
作用:确保本条指令所修饰的成员不会在编译的时候被省略,且每次都会直接读取它的值
防止编译器优化此代码的风险
*/
private volatile static DCLSingleton instance;
private DCLSingleton (){}
public static synchronized DCLSingleton getInstance(){
if (instance == null){
synchronized (DCLSingleton.class){
if (instance == null){
instance = new DCLSingleton();
}
}
}
return instance;
}
public void showMessage(){
System.out.println("创建instance成功,showMessage执行了");
}
}
2.从DCLSingleton类获取唯⼀的对象。
package com.设计模式.双检锁;
public class Test_DCLSingleton {
public static void main(String[] args) {
DCLSingleton dclSingleton = DCLSingleton.getInstance();
dclSingleton.showMessage();
}
}
3.执⾏程序,输出结果。
3.4 登记式/静态内部类
内部类只有在外部类被调用才加载,产生INSTANCE实例,又不用加锁,此模式有懒汉式 和饿汉式的优点,屏蔽了他们的缺点,是最好的单例模式
3.4.1 登记式/静态内部类介绍
是否Lazy初始化:是 (在一开始的时候内部类不会被加载,内部类只有被调用的时候才会被加载)
是否多线程安全:是 (classloader机制为其自动加了锁)
描述:这种⽅式能达到双检锁⽅式⼀样的功效,但实现更简单。对静态域使⽤延迟初始化,应使⽤这种⽅式⽽不是双检锁⽅式。这种⽅式只适⽤于静态域的情况,双检锁⽅式可在实例域需要延迟初始化时使⽤。 这种⽅式同样利⽤了classloader机制来保证初始化instance时只有⼀个线程,它跟饿汉式不同的是:饿汉式只要HungrySingleton类被装载了,那么instance就会被实例化(没有达到lazy loading效果),⽽这种⽅式是RegisterSingleton类被装载了,instance不⼀定被初始化。因为Test_RegisterSingleton类没有被主动使⽤,只有通过显式调⽤getInstance⽅法时,才会显式装载SingletonHolder类,从⽽实例化instance。想象⼀下,如果实例化instance很消耗资源,所以想让它延迟加载,另外⼀⽅⾯,还要保证多线程安全,因为不能确保Singleton类还可能在其他的地⽅被主动使⽤从⽽被加载,那么这个时候实例化instance显然是不合适的。这个时候,这种⽅式相⽐饿汉式⽅式就显得很合理。
3.4.2 登记式/静态内部类案例
1.创建⼀个RegisterSingleton类。
package com.设计模式.登记式;
/*
* 登记式/静态内部类:
* 1.在单例类中声明一个静态内部类
* 2.在这个静态内部类中声明单例对象,并且初始化这个对象
* */
public class RegisterSingleton {
private static class SingletonHolder {
//创建私有化的静态常量
private static final RegisterSingleton INSTANCE = new RegisterSingleton();
}
private RegisterSingleton (){}
//final修饰的方法不能被重写
public static final RegisterSingleton getInstance(){
return SingletonHolder.INSTANCE;
}
public void showMessage(){
System.out.println("INSTANCE创建成功,showMessage执行了");
}
}
2.从RegisterSingleton类获取唯⼀的对象。
package com.设计模式.登记式;
public class Test_RegisterSingleton {
public static void main(String[] args) {
RegisterSingleton registerSingleton = RegisterSingleton.getInstance();
registerSingleton.showMessage();
}
}
3.执⾏程序,输出结果。
3.5 枚举式
3.5.1 枚举式介绍
是否Lazy初始化:否
是否多线程安全:是
描述:这种实现⽅式还没有被⼴泛采⽤,但这是实现单例模式的最佳⽅法。它更简洁,⾃动⽀持序列化机制,绝对防⽌多次实例化。 这种⽅式是Effective Java作者Josh Bloch提倡的⽅式,它不仅能避免多线程同步问题,⽽且还⾃动⽀持序列化机 制,防⽌反序列化重新创建新的对象,绝对防⽌多次实例化。不过由于JDK1.5后才加⼊enum特性,⽤这种⽅式写 不免让⼈感觉⽣疏,在实际⼯作中,也很少⽤。 不能通过reflection attack来调⽤私有构造⽅法。
3.5.2 枚举式案例
1.创建⼀个EnumSingleton枚举类。
package com.设计模式.枚举式;
public enum EnumSingleton {
//枚举常量可以作为枚举内部定义方法的访问
INSTANCE;
/*
* 1.枚举类型中定义的变量只能是常量
* 2.枚举类型中定义方法(和class类结构中定义的方法完全一样)
* */
public void showMessage(){
System.out.println("EnumSingleton类中的showMessage");
}
}
2.从EnumSingleton枚举类获取唯⼀的枚举类型数据。
package com.设计模式.枚举式;
public class Test_EnumSingleton {
public static void main(String[] args) {
EnumSingleton.INSTANCE.showMessage();
}
}
3.执⾏程序,输出结果。