浅谈GOF设计模式之单例模式
核心作用及应用场景
核心作用
保证一个类只有一个实例,并且提供一个访问该实例的全局访问点
常见应用场景
1.Windows的Task Manager(任务管理器)就是很典型的单例模式
2.windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例
3.网站的计数器(例如实时在线人数等),一般也是采用单例模式实现,否则难以同步。
4.应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作
,否则内容不好追加。
5.数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源
6.操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
7.servlet 中的 Application 也是单例的典型应用
8.在Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理
9.在servlet编程中,每个Servlet也是单例
10.在spring MVC框架/struts1框架中,控制器对象也是单例
单例模式的优点
1.由于单例模式只生成一个实例,减少了系统性能开销,
当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决
– 单例模式可以在系统设置全局的访问点,优化环共享资源访问,例如可以设计一个单例类,负责所有数据表的映射处理
常见的五种单例模式实现方式:
推荐静态内部类(效率高仅次于饿汉式),和枚举方式(枚举还能防破解)得方式
主要:
• 饿汉式(线程安全,调用效率高。 但是,不能延时加载。)
• 懒汉式(线程安全,调用效率不高。 但是,可以延时加载。)
– 其他:
• 双重检测锁式(由于JVM底层内部模型原因,偶尔会出问题。不建议使用)
• 静态内部类式(线程安全,调用效率高。 但是,可以延时加载)
• 枚举单例(线程安全,调用效率高,不能延时加载)
单例模式的选用:
_单例对象 占用 资源 少,不需要 延时加载:
• 枚举式 好于 饿汉式
– 单例对象 占用 资源 大,需要 延时加载:
• 静态内部类式 好于 懒汉式
五种单例的实现以及优缺点
饿汉模式(单例对象立即加载)
1.饿汉模式(单例对象立即加载),线程安全的(创建出来之家在一次),调用效率高,不能掩饰加载,缺点是不调用的时候也会加载
2.在额韩式单例中,static变量会在类装载时初始化,此时也不会设计多个线程对象访问该对象的问题,虚拟机保证只会蝗灾一次该类,肯定不会发生并发访问的问题,可以胜率 synchronized (同步锁)关键字
3.如果只是加载本类,而事实调用getInstance() ,甚至永远没有调用,就会造成资源浪费
/**
*
* @author 季久亮<jijiuliang@sohu.com> 2019年4月9日
*
*
* 饿汉模式
*/
//饿汉式(静态变量实现)
public class Single {
//类初始化时,立即加载这个对象(一次),这个类时,天然的线程安全
//静态成员加载 天然的是线程安全的,效率高
private static /*final*/ Single instance = new Single(); //提供私有化的静态属性存放实例 -- 单例
//方法没有同步,调用效率高
//只能通过公共的静态的方法获取单例
public static Single getInstance() {
return instance;
}
// 将构造器设置为private禁止通过new进行实例化
private Single() {
}
}
测试
public class SingletonTest01 {
public static void main(String[] args) {
//测试
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2); // true
System.out.println("instance.hashCode=" + instance.hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode());
}
}
//饿汉式(变种 静态块实现)
class Singleton {
//1. 构造器私有化, 外部不能new
private Singleton() {
}
//2.本类内部创建对象实例
private static Singleton instance;
static { // 在静态代码块中,创建单例对象
instance = new Singleton();
}
//3. 提供一个公有的静态方法,返回实例对象
public static Singleton getInstance() {
return instance;
}
}
输出
true
instance.hashCode=366712642
instance2.hashCode=366712642
懒汉式单例(单例对象立即加载)
lazy load:延时加载,懒加载!真正用的时候才加载
资源利用率太高了,但是每次调用getInstance()方法都要同步,并发效率低,调用效率低
== 第一种线程不安全的==
public class SingletonTest04 {
public static void main(String[] args) {
System.out.println("懒汉式2 , 线程安全~");
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2); // true
System.out.println("instance.hashCode=" + instance.hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode());
}
}
//线程不安全
//懒汉式
class Singleton {
//成员对象
private static Singleton instance;
//初始化私有化构造器,只能内部创建
private Singleton() {}
//提供一个静态的公有方法,当使用到该方法时,才去创建 instance
//即懒汉式
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
//线程安全 效率低
// 懒汉式(线程安全,同步方法)
class Singleton {
private static Singleton instance;
private Singleton() {}
//提供一个静态的公有方法,加入同步处理的代码,解决线程安全问题
//即懒汉式 调用效率低 synchronized 同步锁
public static synchronized Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
静态内部类(线程安全,调用效率高,可以延时加载)懒加载
==lazy load:延时加载,外部类没有static属性不会像饿汉式一样立即加载对象=
只有真正调用getInstance()才会加载静态内部类,加载类时是线程安全的,instance是static final 类型的,保证看了内崔中只有一个实例存在,而且只能复制一次,所以是线程安全的
兼并了并发高效调用个延时加载的优势
//调用的时候不要加同步synchronized,类加载的过程是天然的线程安全的不需要多此一举
public class SingletonTest07 {
public static void main(String[] args) {
System.out.println("使用静态内部类完成单例模式");
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2); // true
System.out.println("instance.hashCode=" + instance.hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode());
}
}
// 静态内部类完成, 推荐使用
class Singleton {
private static volatile Singleton instance;
//构造器私有化
private Singleton() {}
//写一个静态内部类,该类中有一个静态属性 Singleton
private static class SingletonInstance {
private static final Singleton INSTANCE = new Singleton();
}
//提供一个静态的公有方法,直接返回SingletonInstance.INSTANCE
//调用的时候不要加同步synchronized,类加载的过程是天然的线程安全的不需要多此一举
public static /*synchronized*/ Singleton getInstance() {
return SingletonInstance.INSTANCE;
}
}
双重检查锁模式()
错误写法
public class SingletonTest06 {
public static void main(String[] args) {
System.out.println("双重检查");
Singleton instance = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance == instance2); // true
System.out.println("instance.hashCode=" + instance.hashCode());
System.out.println("instance2.hashCode=" + instance2.hashCode());
}
}
// 懒汉式(线程安全,同步方法)
class Singleton {
private static volatile Singleton instance;
private Singleton() {}
//提供一个静态的公有方法,加入双重检查代码,解决线程安全问题, 同时解决懒加载问题
//因为 synchronized 还是加载方法 导致每次调用依然是同步,效率跟懒汉式一样低下
//网上有很多以为这是双重检查锁的写法实际上这是错误的,根本没有意义,与想解决的问题根本就是冲突的,只是双重检查而已
public static synchronized Singleton getInstance() {
if(instance == null) {
synchronized (Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
正确写法
由于编译器优化问题和JVM底层内部模型缺陷,导致偶尔会出问题,不建议使用
实现原理将同步内容下方if内部,不用每次获取对象时同步,只有第一次创建时同步,提高了执行效率
//正确的写法
//由于编译器优化问题和JVM底层内部模型缺陷,导致偶尔会出问题,不建议使用
//实现原理将同步内容下方if内部,不用每次获取对象时同步,只有第一次创建时同步,提高了执行效率
public static Singleton getInstanceTrue() {
if(instance == null) {
Singleton sc;
synchronized (Singleton.class){
sc=instance;
if(sc == null) {
synchronized (Singleton.class) {
if (sc == null) {
sc = new Singleton();
}
}
}
}
instance = sc;
}
return instance;
}
枚举方式(天然的单例,实现简单)
优点:实现简单
枚举本身就是单例模式,由jvm从根本上调用提供保障!避免通过反射和反序列化的漏洞
缺点:无延时加载
public class SingletonTest08 {
public static void main(String[] args) {
Singleton instance = Singleton.INSTANCE;
Singleton instance2 = Singleton.INSTANCE;
System.out.println(instance == instance2);
System.out.println(instance.hashCode());
System.out.println(instance2.hashCode());
instance.sayOK();
}
}
//使用枚举,可以实现单例, 推荐
//因为枚举在反编译后,每个枚举值都是抽象枚举类类型的 static final 的变量,所以天然的线程安全
enum Singleton {
INSTANCE; //属性
public void sayOK() {
System.out.println("ok~");
}
}
测试时间
饿汉式 22ms
懒汉式 636ms
静态内部类式 28ms
枚举式 32ms
双重检查锁式 65ms
破解单例模式的几个问题
问题:
反射可以破解上面几种(不包含枚举式)实现方式!(可以在构造方法中手动抛出异常控制)
反序列化可以破解上面几种((不包含枚举式))实现方式!
• 可以通过定义readResolve()防止获得不同对象。
– 反序列化时,如果对象所在类定义了readResolve(),(实际是一种回调),定义返回哪个对象。
public class SingletonDemo01 implements Serializable {
private static SingletonDemo01 s;
private SingletonDemo01() throws Exception{
if(s!=null){
throw new Exception("只能创建一个对象");
//通过手动抛出异常,避免通过反射创建多个单例对象!
}
} //私有化构造器
public static synchronized SingletonDemo01 getInstance() throws Exception{
if(s==null){
s = new SingletonDemo01();
}
return s;
}
//反序列化时,如果对象所在类定义了readResolve(),(实际是一种回调),定义返回哪个对象。
private Object readResolve() throws ObjectStreamException {
return s;
}
}
验证反射和反序列化
public class Client2 {
public static void main(String[] args) throws Exception {
SingletonDemo6 s1 = SingletonDemo6.getInstance();
SingletonDemo6 s2 = SingletonDemo6.getInstance();
System.out.println(s1);
System.out.println(s2);
//通过反射的方式直接调用私有构造器
// Class<SingletonDemo6> clazz = (Class<SingletonDemo6>) Class.forName("com.bjsxt.singleton.SingletonDemo6");
// Constructor<SingletonDemo6> c = clazz.getDeclaredConstructor(null);
// c.setAccessible(true);
// SingletonDemo6 s3 = c.newInstance();
// SingletonDemo6 s4 = c.newInstance();
// System.out.println(s3);
// System.out.println(s4);
//通过反序列化的方式构造多个对象
FileOutputStream fos = new FileOutputStream("d:/a.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s1);
oos.close();
fos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/a.txt"));
SingletonDemo6 s3 = (SingletonDemo6) ois.readObject();
System.out.println(s3);
}
}
多线程场景下测试各个单例的效率
public class Client3 {
public static void main(String[] args) throws Exception {
long start = System.currentTimeMillis();
int threadNum = 10;
final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
for(int i=0;i<threadNum;i++){
new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<1000000;i++){
// Object o = SingletonDemo4.getInstance();
Object o = SingletonDemo5.INSTANCE;
}
countDownLatch.countDown();
}
}).start();
}
countDownLatch.await(); //main线程阻塞,直到计数器变为0,才会继续往下执行!
long end = System.currentTimeMillis();
System.out.println("总耗时:"+(end-start));
}
}