单例模式(Singleton Pattern)
定义:确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点
应用场景:例数据库连接池,ServletContext,Spring框架中的ApplicationContext
需要频繁创建的一些类,使用该模式可以降低系统的内存,GC压力
实例化代价昂贵的对象
频繁进行数据库访问和IO操作的对象
使用方式:私有化构造方法,提供一个静态全局访问方法来返回对象,保证对象只会实例化一次
分支实现:
饿汉式:对象初始化在类加载阶段就进行绝对的线程安全,如使用静态代码块,缺点是单例对象不管会不会使用到,都会创建对象而浪费了内存空间。
懒汉式:在全局访问点被调用时,才会去第一次初始化对象,但是在多线程环境下会出现线程安全问题,因为在多个线程几乎同时调用getinstance()方法时,判空条件会同时满足,并都会去实例化对象从而先后实例化出两个单例对象
(tip:线程调试模式?)
给getinstance()方法加上synchronized及可以解决线程安全问题,但是当在高并发环境下由于锁定的是class对象,一调用静态方法,便可能会导致大批线程阻塞,从而导致性能下降
双重锁验证单例模式:
给单例静态属性加上volatile修饰,避免new操作的指令重排,getinstance方法中第一次判空减少高并发环境下同时进入后面synchronized阻塞的机会,在进入之后第二次判空判断是否单例对象还未初始化,这种方式优化了性能压力,同时兼顾线程安全问题
package com.Singleton;
/**
* @author yangxiaozhen
* @date 2022/5/4-23:13
*/
public class LazyDoubleCheckSingleton {
private volatile static LazyDoubleCheckSingleton instance;
private LazyDoubleCheckSingleton() {
}
public static LazyDoubleCheckSingleton getInstance() {
if (instance == null) {
synchronized (LazyDoubleCheckSingleton.class) {
if (instance == null) {
instance = new LazyDoubleCheckSingleton();
}
}
}
return instance;
}
}
更好的解决方案:采用静态内部类的方式:
package com.Singleton;
/**
* @author yangxiaozhen
* 内部类形式实现单例模式解决线程安全问题和锁的性能问题
* @date 2022/5/4-23:46
*/
public class LazyStaticInnerClassSingleton {
private LazyStaticInnerClassSingleton() {
}
public static LazyStaticInnerClassSingleton getInstance() {
return InnerClassSingleton.instance;
}
private static class InnerClassSingleton {
static final LazyStaticInnerClassSingleton instance = new LazyStaticInnerClassSingleton();
}
}
(tip:内部类的类加载与其外部内无关,它只在确认被使用时(如调用静态变量,或静态方法时)才进行类加载并只进行一次)
但是简单的这样实现,还是可能被反射破坏掉单例模式的初衷因为私有的构造方法可以在反射中破环掉访问限权进而暴力初始化-->继续升级,在构造方法中判断内部内的静态单例变量是否已初始化(判空),若已初始化过,则抛出runtimeException
目前最优雅的单例模式实现是:枚举式单例模式(利用枚举的语法优势)
通过反编译可以看到默认生成了一段静态代码块与饿汉式实现类似
package com.Singleton;
/**
* @author yangxiaozhen
* @date 2022/5/5-22:10
*/
public enum EnumSingleton {
INSTANCE;
private Object date;
public Object getDate() {
return date;
}
public void setDate(Object date) {
this.date = date;
}
public static EnumSingleton getInstance(){
return INSTANCE;
}
}
static
{
INSTANCE=new EnumSingleton("INSTANCE",0);
$VALUE = (new EnumSingleton[]{
INSTANCE
)};
}
package com.Singleton;
/**
* @author yangxiaozhen
* @date 2022/5/6-21:25
* 最优美的单例模式写法,不会被new 反射 序列化 和clone破坏掉
*/
public class SingletonObjectByEnum {
private SingletonObjectByEnum(){
}
enum singleton{
SINGLETON;
private final SingletonObjectByEnum instance;
singleton(){
instance = new SingletonObjectByEnum();
}
private SingletonObjectByEnum getInstance(){
return instance;
}
}
public static SingletonObjectByEnum getInstance(){
return singleton.SINGLETON.instance;
//return singleton.SINGLETON.getInstance;
}
}
还有容器式单例模式实现,但是个人感觉没有多大意义,若用hashmap/concurrenthashmap实现则多线程环境下还是可能被破坏,用hashtable实现的话有影响性能
其他知识点:
1.创建一个对象的方式有:new、克隆、序列化、反射
2.虽然clone()是Object的方法,也就是说每个对象都拥有一个克隆方法,但是某一个对象直接调用clone方法,会抛出异常,即并不能成功克隆一个对象。调用该方法时,必须实现一个Cloneable 接口。这也就是原型模式的实现方式。还有即如果该类实现了cloneable接口,尽管构造函数是私有的,他也可以创建一个对象。即clone方法是不会调用构造函数的,他是直接从内存中copy内存区域的。