单例模式的课堂笔记
单例模式概述
确保一个类只有一个实例,并且提供一个全局访问点来访问这个唯一实例。
对于一个软件系统要求只有一个实例类,例如:任务管理器。
单例模式的结构与实现
提出问题
抛出问题:如何保证一个类只有一个实例呢?
引导大家思考
一个类,既然只能由一个实例,就表明他在client(客户端类)里面不能调用这个类的构造方法。不然client类可以无数次创建这个类的对象。
那么,我们就顺利成章的想到将这个构造方法私有化——定义为private。
单例模式最重要的思想就是:构造器私有。只有这样才能确保用户无法通过new关键字实例化它。(但是通过反射可以创建实例,后面会写)。
继续思考:既然构造器私有,那么外面就不能创造这个对象。这样这个对象就只能写在类里面。那么问题来了这个成员变量是用public还是private(大家可以参考这篇文章http://t.csdn.cn/SoeMF)
既然这样我们就会用一个private修饰这个成员对象
继续思考:这个时候我们还差一个方法来访问
要想访问一个类中的方法有两个方式
- new一个对应类的对象,通过对象.方法名()来调用我们的尘缘方法
- 类名.方法名来调用成员方法
因为构造器私有了,所以我们自然而然只能使用第二种方向,所以我们返回单例对象的方法必须用static修饰。
这样我们的方法就是一个静态类方法,而静态类方法又不能调用非静态类的成员变量,所以我们的成员变量最后就是private static去修饰
代码具体实现
最终展示类的定义
。
单例模式的实现方法
单例模式能确保一个类只有一个实例,而且它能自行实例化并向整个系统提供这个实例。在计算机系统中的连接池、缓存、日志、打印机等管理类通常被设计成单例类。一家企业只有一位首席执行官(CEO),请使用懒汉式定义单例类CEO,属性自行定义,对象ceo的方法是主持董事会议holdAMeeting()。
public class CEO {
private volatile static CEO chairman;
private CEO(){
}
public static CEO getBean() {
if (chairman == null) {
synchronized (CEO.class) {
if (chairman == null) {
chairman = new CEO();
}
}
}
return chairman;
}
public void holdAMeeting(){
System.out.println("hold a meeting");
}
public static void main(String[] args) {
CEO chairman=CEO.getBean();
chairman.holdAMeeting();
}
}
1.饿汉式单例(不推荐)
在定义静态变量的时候实例化单例类,即类加载时单例对象就已创建。
缺点
由于饿汉式单例,在使用之前就申请好内存空间了,但有可能用不到,占用资源。所以提出懒汉式单例
2.懒汉式单例(不推荐)
要用的时候给它实例化
提出问题
这个代码单线程下是没有问题的,但是多线程就会出问题
这里我举个例子,大家就可以明白为什么多线程下会出问题了。
public class SingletonPattern {
private static SingletonPattern instance;
private SingletonPattern(){
System.out.println(Thread.currentThread().getName()+" is instantiated");
}
private static SingletonPattern getInstance(){
if(instance==null){
instance=new SingletonPattern();
}
return instance;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()-> SingletonPattern.getInstance()).start();
}
}
}
结果截图
结果可以发现,在抢占cpu时,有多个线程发现instance==null,所以都调用了构造方法。
这个时候我们就需要加锁synchronized,这个时候就提出了我们的双重检查锁定(Double-Check Locking)
3.(DCL)双重检查锁定(推荐)
private static SingletonPattern getInstance(){
if(instance==null){
synchronized (SingletonPattern.class){
if(instance==null){
instance=new SingletonPattern();
}
}
}
return instance;
}
提出问题
但是这个代码还是有问题
大家知道多线程高并发有三要素
- 原子性:指的是⼀个或者多个操作,要么全部执⾏并且在执⾏的过程中不被其他操作打断,要么就全部不执⾏。原⼦性是数据⼀致性的保障。
- 有序性:程序的执⾏顺序按照代码的先后顺序来执⾏。单线程简单的事,多线程并发就不容易保障了。
- 可见性:指多个线程操作⼀个共享变量时,其中⼀个线程对变量进⾏修改后,其他线程可以⽴即看到修改的结果。(线程间的通信实现)
显然可见这段代码不满足原子性和有序性。
instance=new SingletonPattern();
创建对象的这个操作分三步
- 在heap(堆)中先申请一个内存空间
- new 执行构造方法
- 把实例化的对象与内存空间关联。
有可能是132或者123的顺序。所以这里就引出了修饰符volatile
volatile修饰符
继续提出问题
但是通过反射依旧可以破坏它,具体看代码
import java.lang.reflect.Constructor;
public class SingletonPattern {
private volatile static SingletonPattern instance;
private SingletonPattern(){
}
private static SingletonPattern getInstance(){
if(instance==null){
synchronized (SingletonPattern.class){
if(instance==null){
instance=new SingletonPattern();
}
}
}
return instance;
}
public static void main(String[] args) throws Exception {
SingletonPattern instance=SingletonPattern.getInstance();
Constructor<SingletonPattern> declaredConstructor = SingletonPattern.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
SingletonPattern instance2=declaredConstructor.newInstance();
System.out.println(instance2.hashCode());
System.out.println(instance.hashCode());
}
}
对无参构造器下文章
反射其实走的就是Singleton的私有无参构造器,我们在无参构造器里也实现类锁,只要instance被实例化了,下一次就没法再走无参构造器
private static SingletonPattern getInstance(){
if(instance==null){
synchronized (SingletonPattern.class){
if(instance==null){
instance=new SingletonPattern();
}
}
}
return instance;
}
继续提出问题
其实两个对象都用反射获得,依旧能破坏。解决思想是:设置一个成员变量开关,代码大家可以自己去写一写。评论区给大家回复
4.静态内部类实现单例模式
这里我就不介绍了,挺简单的
5.枚举
枚举是实现单例模式最安全的方法
public enum enumSingleton {
ENUM_SINGLETON;
public enumSingleton getEnumSingleton(){
return ENUM_SINGLETON;
}
}
缺点
枚举类确实能够防止序列化和多线程的问题,但同时它因为它继承了lang.Enum,是一个final class,所以不允许其他类继承它。
单例模式应用实例(作业)
单例模式能确保一个类只有一个实例,而且它能自行实例化并向整个系统提供这个实例。在计算机系统中的连接池、缓存、日志、打印机等管理类通常被设计成单例类。一家企业只有一位首席执行官(CEO),请使用懒汉式定义单例类CEO,属性自行定义,对象ceo的方法是主持董事会议holdAMeeting()。
public class CEO {
private volatile static CEO chairman;
private CEO(){
}
public static CEO getBean() {
if (chairman == null) {
synchronized (CEO.class) {
if (chairman == null) {
chairman = new CEO();
}
}
}
return chairman;
}
public void holdAMeeting(){
System.out.println("hold a meeting");
}
public static void main(String[] args) {
CEO chairman=CEO.getBean();
chairman.holdAMeeting();
}
}