一、单例模式介绍
1、定义
单例模式是Java中最简单的设计模式之一,此模式保证 某个类在运行期间,只有一个实例对外提供服务,而这个类被称为单例类。
2、使用单例模式要做的两件事
- 保证一个类只有一个实例
- 为该实例提供一个全局访问点
二、饿汉式
1、特点
在类加载期间初始化私有的静态实例,保证instance实例创建过程式线程安全的。
// 饿汉式
public class Singleton01 {
// 私有化构造方法:禁止外部通过new的方式创建对象
private Singleton01(){}
// 通过static 在类加载期间创建私有化静态实例
// 静态实例属于类,在类加载时会创建出来,并且只创建一次。这样可以保证线程安全,因为拿到的始终时同一个对象
private static Singleton01 instance = new Singleton01();
// 提供全局访问点
public static Singleton01 getInstance(){
return instance;
}
}
二、懒汉式(线程不安全)
1、特点
支持懒加载(延迟加载),只有调用getInstance方法时,才会创建对象
2、问题
在多线程下,会有线程安全问题。
线程A 进入if判断后,还没new操作。线程B也通过if判断了。这样线程A、B 都会进行new操作。获取到两个不同的对象
// 懒汉式
public class Singleton02 {
// 私有化构造方法:防止外部通过new的方式创建对象
private Singleton02(){}
// 不再这里创建对象了,调用getInstance方法时才创建
private static Singleton02 instance;
// 在调用getInstance方法中,判断没创建对象就新new一个对象返回
public static Singleton02 getInstance(){
if (instance == null){
return new Singleton02();
}
return instance;
}
}
三、懒汉式(线程安全)
1、特点
通过使用synchronized 锁,锁住单例模式对象的方法,防止多个线程同时调用问题。
2、缺点
对方法加了synchronized锁,并发就很低。
class Singleton03{
private Singleton03(){}
private static Singleton03 instance;
//通过添加 synchronized 保证多线程模式下,单例模式对象的唯一性。
public static synchronized Singleton03 getInstance(){
if (instance == null){
return new Singleton03();
}
return instance;
}
}
四、懒汉式-双重校验
// 懒汉式 双重校验
public class Singleton04{
private Singleton04(){}
// 加volatile保证变量的可见性、屏蔽指令重排序
private volatile static Singleton04 instance;
// 通过添加 synchronized 保证多线程模式下,单例模式对象的唯一性。
public static Singleton04 getInstance(){
// 第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
if (instance == null){
// 第二次判断要等到抢到锁之后。
synchronized (Singleton04.class) {
if (instance == null) {
instance = new Singleton04();
/**
* 上面的创建对象的代码,在JVM 中被分为三步:
* 1、分配内存空间
* 2、初始化对象
* 3、将instance指向分配好的内存空间
*/
}
}
}
return instance;
}
}
五、静态内部类(懒加载)
静态内部类的特性:在静态内部类中创建单例,在转载内部类的时候,才会创建单例对象。
就是只有在调用Singleton4Static 的时候才会去new 对象。
/**
* 单例模式- 静态内部类 (懒加载)
* 根据静态内部类的特性,同时解决了 延时加载 程序安全的问题,并且代码更加简洁
*/
class Singleton05{
private Singleton05(){}
// 创建静态内部类
private static class Singleton4Static{
private static Singleton05 instance = new Singleton05();
}
public static Singleton05 getInstance(){
return Singleton4Static.instance;
}
}
六、反射对单例对象的破坏
反射破坏的原理
突破封装:
setAccessible(true)
覆盖了Java的访问控制检查绕过初始化逻辑:直接调用构造方法,跳过了静态内部类的初始化控制
类加载机制失效:静态内部类方案依赖类加载保证单例,但反射在运行时创建新实例
class Reflect{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<Singleton05> clazz = Singleton05.class;
Constructor<Singleton05> constructor = clazz.getDeclaredConstructor();
// 设置为true后就可以对 类中的私有成员进行操作了。
constructor.setAccessible(true);
Singleton05 instance01 = constructor.newInstance();
Singleton05 instance02 = constructor.newInstance();
System.out.println(instance01 == instance02);
}
}
七、防止反射破坏(以Singleton05代码举例)
在私有无参构造方法中加一个判断。
class Singleton05{
private Singleton05(){
if (Singleton4Static.instance != null){
throw new RuntimeException("不允许非法访问");
}
}
// 创建静态内部类
private static class Singleton4Static{
private static Singleton05 instance = new Singleton05();
}
public static Singleton05 getInstance(){
return Singleton4Static.instance;
}
}