00017.11单例模式(单例设计模式)——必须要能够自己手敲的必学知识

本文深入探讨了Java中的单例模式,包括饿汉式和懒汉式的不同实现方式。饿汉式通过立即创建实例确保线程安全,而懒汉式则延迟创建,按需加载。文章详细分析了懒汉式的线程安全问题及解决方案,如双重检查锁定。同时,还介绍了使用枚举实现单例的简洁和安全。最后,提供了多种单例模式的代码示例。
摘要由CSDN通过智能技术生成

系列文章目录

前言

1、它是设计模式里面最简单的一种;
2、它是高频面试题,这也就是为什么单例模式要求必须自己能够完全手写出来的原因
3、说白了饿/恶汉式就是枚举,第1种是正常的枚举、第2种是旧版的枚举、第3种是旧版本的基础上方法私有化

一、单例模式是什么?

1、这里可以涉及到很久以前一档节目叫《非你莫属》里面有个程序员直接用脚把单例模式写出来了,所以后来这道题就直接火了
2、初学单例模式可以跟枚举联系起来,枚举是指某个类型的对象有且仅有固定的几个,如果固定一个,这就是单例了
在这里插入图片描述
上图,它只有一个对象,就可以理解为单例了

在这里插入图片描述
前面是单例最简单的一种,但是我们的单例不止这一种

我们分类一下

1、饿/恶汉式

不管我们使用者是否需要这个对象,它都一上来就给你创建好这个唯一的对象
(1)比如上面的枚举类型就是
(2)
a、构造器私有化
b、用一个全局的静态常量来保存这个唯一的实例对象
实际上也是枚举如下,1.5之前实现枚举也只能这样:

在这里插入图片描述
在这里插入图片描述
(3)形式三
a、构造器私有化
b、用一个私有的静态的常量,来保存这个唯一的实例对象
c、提供一个静态方法,来返回这个常量对象
在这里插入图片描述
通过方法去获取,而不是直接暴露
在这里插入图片描述
在这里插入图片描述
饿/恶汉式就这三种
下面再来说说

2、懒汉式

非常重要,能不创建就不创建,免得浪费内存

延迟创建对象。当使用者来或者这个对象,要用到对象时,我再创建。
(1)形式一:
见下面,考虑线程安全问题和性能问题
在这里插入图片描述

在这里插入图片描述
因为掉一次new一次肯定不是同一个对象了
所以最终是这样的,这个也是标准的单例模式
在这里插入图片描述
在绝大多事情况下这个确实没有问题,但是……
存在一个线程安全问题,不知道大家有没有发现

在这里插入图片描述
我们重现一下错误,演示一下
这里休眠一下(自己try-catch一下)
在这里插入图片描述
匿名内部类,运用多态给它赋值
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
那怎么解决呢?
加锁给它锁上
把这个方法锁上,同一时间就只能进来一个线程
把这个方法锁上,同一时间就只能进来一个线程]
这样就可以了
但是……
安全问题解决了,发烧友还是认为影响了性能,性能不够快
因为有可能我这个线程以及创建好了,后面的线程没有必要排队,你理一理,是不是这个道理,所以还可以再加判断
之前第一次第二次,前面两个线程有竞争关系,后面没必要每次都锁
安全没有问题,但是认为不是最优的

3、公认优化版

在这里插入图片描述
为什么不能用this?
静态方法里面能出现this吗? 不能
解决办法:使用当前类的Class对象
在这里插入图片描述
效率还不够高再加一个条件判断,双重保险
在这里插入图片描述
缺点,这种模式嵌套的层数较多,可能稍微有点模糊,我们再看一下形式二,看看是不是会简单一些
(2)形式二:内部类形式
在这里插入图片描述
内部类的初始化,必须你用到它的时候它才会初始化
在这里插入图片描述
这就是线程安全的,并且简洁的懒汉式
这样我们就讲了5种形式

  • 1、饿/恶汉式
  • 不管我们使用者是否需要这个对象,它都上来先给你创建好这个唯一的对象。
  • (1)枚举类型
  • (2)形式二
  • ①构造器私有化
  • ②用一个全局的静态的常量,来保存这个唯一的实例对象
  • (3)形式三
  • ①构造器私有化
  • ②用一个私有的静态的常量,来保存这个唯一的实例对象
  • ③提供一个静态方法,来返回这个常量对象
  • 2、懒汉式
  • 延迟创建对象。当使用者来或者这个对象,要用到对象时,我再创建。
  • (1)形式一:
  • 见下面,考虑线程安全问题和性能问题
  • (2)形式二:内部类形式

二、代码

package com.atguigu.test17;

import org.junit.Test;

/*
 * 单例设计模式:
 * 
 * 单例:某个类只能有唯一的一个实例对象。
 * 
 * 如何实现单例?
 * 1、饿/恶汉式
 * 不管我们使用者是否需要这个对象,它都上来先给你创建好这个唯一的对象。
 * (1)枚举类型
 * (2)形式二
 * ①构造器私有化
 * ②用一个全局的静态的常量,来保存这个唯一的实例对象
 * (3)形式三
 * ①构造器私有化
 * ②用一个私有的静态的常量,来保存这个唯一的实例对象
 * ③提供一个静态方法,来返回这个常量对象
 * 
 * 2、懒汉式
 * 延迟创建对象。当使用者来或者这个对象,要用到对象时,我再创建。
 * (1)形式一:
 * 见下面,考虑线程安全问题和性能问题
 * (2)形式二:内部类形式
 * 
 */
public class Test17 {
	@Test
	public void test1(){
		SingleEnum s1 = SingleEnum.INSTANCE;
		SingleEnum s2 = SingleEnum.INSTANCE;
		System.out.println(s1 == s2);
	}
	
	@Test
	public void test2(){
//		SingleEnum.test();//此时我并没有需要用到这个对象,但是它也创建出来了
	}
	
	@Test
	public void test3(){
		SingleClass s1 = SingleClass.INSTANCE;
		SingleClass s2 = SingleClass.INSTANCE;
		System.out.println(s1==s2);
	}
	
	@Test
	public void test4(){
		Single s1 = Single.getInstance();
		Single s2 = Single.getInstance();
		System.out.println(s1 == s2);
	}
	
	@Test
	public void test5(){
		LazyClass s1 = LazyClass.getInstance();
		LazyClass s2 = LazyClass.getInstance();
		System.out.println(s2 == s1);
	}
	
	LazyClass s1;
	LazyClass s2;
	@Test
	public void test6(){
		//匿名的内部类,继承Thread类
		Thread t1 = new Thread(){
			public void run(){
				s1 = LazyClass.getInstance();
			}
		};
		
		Thread t2 = new Thread(){
			public void run(){
				s2 = LazyClass.getInstance();
			}
		};
		
		t1.start();
		t2.start();
		
		try {
			//这里用join的目的是,为了两个子线程都执行完,再执行主线程的System.out.println(s1);
			t1.join();
			t2.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println(s1);
		System.out.println(s2);
		System.out.println(s1 == s2);
	}
	
}
enum SingleEnum{
	INSTANCE;
//	public static void test(){
//		//..
//	}
}
class SingleClass{
	public static final SingleClass INSTANCE = new SingleClass();
	private SingleClass(){
		
	}
}
class Single{
	private static final Single INSTANCE = new Single();
	private Single(){
		
	}
	public static Single getInstance(){
		return INSTANCE;
	}
}

class LazyClass{
	private static LazyClass instance;
	private LazyClass(){
		
	}
	
	public static LazyClass getInstance(){
		if(instance == null){//提高效率
			synchronized(LazyClass.class){//当前类的Class对象
				if(instance == null){//安全判断
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					instance = new LazyClass();
				}
			}
		}
		return instance;
	}
	
	//安全没问题,但是认为不是最优的
/*	public synchronized static LazyClass getInstance(){
		if(instance == null){
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			instance = new LazyClass();
		}
		return instance;
	}*/
	
	//有安全问题
/*	public static LazyClass getInstance(){
//		return new LazyClass();//错误的
		if(instance == null){
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			instance = new LazyClass();
		}
		return instance;
	}*/
}

class Lazy{
	private Lazy(){
		
	}
	
	private static class Inner{
		public static final Lazy INSTANCE = new Lazy();//在内部类中,创建外部类的唯一对象
	}
	
	public static Lazy getInstance(){
		return Inner.INSTANCE;
	}
}

总结

1、单例模式有很多种,如果面试的时候说让你写一个最简单的,可以参考本文章饿汉式第一种写法,就三四行代码
2、本篇文章懒汉式 形式一是最优化的单例模,本篇文章懒汉式 形式二是最简洁的单例模式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值