单例模式
什么是单例模式:JVM中对象只有一个实例的存在
单例的应用场景:
- 项目中定义的配置文件
- Servlet对象默认为单例
- 线程池、数据库连接池(这里体现了复用机制)
- Spring中的Bean对象
- 一个网站的计数器
- JVM内置缓存框架(定义一个单例的HashMap)
- 枚举(最安全的单例)
单例模式的优缺点:
- 优点:能够节约当前堆内存空间,不需要频繁new对象,能够快速访问;
- 确定:当多个线程访问同一个单例对象的时候可能存在线程安全问题(懒加载的时候)
单例模式特点
- 构造方法私有化
- 实例化的变量引用私有化
- 获取实例的方法共有
单例的写法(7种):
绝非像鲁迅笔下“茴的4种写法”那样毫无意义
- 懒加载(线程不安全版)
- 懒加载(线程安全版)
- 懒加载(双重检验锁)
- 饿汉式
- 静态代码块(饿汉式)
- 静态内部类(懒汉式)
- 枚举实现
懒加载(线程不安全版)
懒汉式基本概念:当真正获取到该对象时,才会创建该对象
public class Singleton01 {
//实例化的变量引用私有化
private static Singleton01 singleton = null;
/**
* 私有化构造函数
*/
private Singleton01() {
}
public static Singleton01 getSingleton() {
if (singleton == null) {
singleton = new Singleton01();
}
return singleton;
}
public static void main(String[] args) {
Singleton01 singleton1 = Singleton01.getSingleton();
Singleton01 singleton2 = Singleton01.getSingleton();
System.out.println(singleton1 == singleton2);
}
}
懒加载(线程安全版)
问题:什么时候会出现线程安全问题?
当第一次new出该对象 并赋值到私有引用变量singleton 后面的所以线程直接获取该singleton对象,不需要重复new
public class Singleton02 {
//实例化的变量引用私有化
private static Singleton02 singleton = null;
/**
* 私有化构造函数
*/
private Singleton02() {
}
// 创建和读取对象都需要获取Singleton01 锁
public static synchronized Singleton02 getSingleton() {
if (singleton == null) {
singleton = new Singleton02();
}
return singleton;
}
public static void main(String[] args) {
Singleton02 singleton1 = Singleton02.getSingleton();
Singleton02 singleton2 = Singleton02.getSingleton();
System.out.println(singleton1 == singleton2);
}
}
懒加载(双重检验锁)
双重校验锁(DCL)能够保证线程安全,只会在创建单例兑现时候上锁,获取该单例对象不会上锁,效率比较高。
volatile(避免了重排序,new操作非原子性操作,防止多个线程进行重排序)
public class Singleton03 {
//实例化的变量引用私有化
private static volatile Singleton03 singleton = null;
/**
* 私有化构造函数
*/
private Singleton03() {
}
// 创建和读取对象都需要获取Singleton01 锁
public static Singleton03 getSingleton() {
if (singleton == null) {
synchronized (Singleton03.class) {
if (singleton == null) {
singleton = new Singleton03();
}
}
}
return singleton;
}
public static void main(String[] args) {
Singleton03 singleton1 = Singleton03.getSingleton();
Singleton03 singleton2 = Singleton03.getSingleton();
System.out.println(singleton1 == singleton2);
}
}
饿汉式
提前创建单例对象,优点:先天性线程安全 缺点:占内存
public class Singleton04 {
// 当我们class被加载时,就会提前创建singleton对象
private static Singleton04 singleton = new Singleton04();
/**
* 私有化构造函数
*/
private Singleton04() {
}
public static Singleton04 getSingleton() {
return singleton;
}
public static void main(String[] args) {
Singleton04 singleton1 = Singleton04.getSingleton();
Singleton04 singleton2 = Singleton04.getSingleton();
System.out.println(singleton1 == singleton2);
}
}
静态代码块(本质就是饿汉式)
public class Singleton05 {
// 当我们class被加载时,就会提前创建singleton对象
private static Singleton05 singleton = null;
static {
singleton = new Singleton05();
System.out.println("static执行");
}
/**
* 私有化构造函数
*/
private Singleton05() {
}
public static Singleton05 getSingleton() {
return singleton;
}
public static void main(String[] args) {
Singleton05 singleton1 = Singleton05.getSingleton();
Singleton05 singleton2 = Singleton05.getSingleton();
System.out.println(singleton1 == singleton2);
}
}
静态内部类(懒汉式)
静态内部类,在Spring框架源码中,经常发现使用静态内部类
其虽然是懒汉式,但具有先天性线程安全
public class Singleton06 {
/**
* 私有化构造函数
*/
private Singleton06() {
}
private static class SingletonHolder {
private static Singleton06 singleton = new Singleton06();
}
public static Singleton06 getSingleton() {
return SingletonHolder.singleton;
}
public static void main(String[] args) {
Singleton06 singleton1 = Singleton06.getSingleton();
Singleton06 singleton2 = Singleton06.getSingleton();
System.out.println(singleton1 == singleton2); //true
}
}
枚举实现
枚举属于目前最安全的单例,不能够被反射(这里指反射创建,可以参考Constructor的newInstance源码,如果类型是Enum会抛出一个异常) 不能序列化和反序列化保证单例
public enum Singleton03 {
INSTANCE;
public void getInstance() {
System.out.println("<<<getInstance>>>");
}
}
创建对象的几种方式
1.直接new对象
2.采用克隆对象
3.使用反射创建对象
4.序列化与反序列化
如何破解单例
public class Singleton01 {
//实例化的变量引用私有化
private static volatile Singleton03 singleton = null;
/**
* 私有化构造函数
*/
private Singleton03() {
}
// 创建和读取对象都需要获取Singleton01 锁
public static Singleton03 getSingleton() {
if (singleton == null) {
synchronized (Singleton03.class) {
if (singleton == null) {
singleton = new Singleton03();
}
}
}
return singleton;
}
public static void main(String[] args) {
Class<?> aClass = Class.forName("top.beau.Singleton01");
Singleton01 singleton03 = (Singleton01) aClass.newInstance();
// 使用反射破解单例
// Singleton01 singleton01 = Singleton01.getSingleton01();
// Singleton01 singleton02 = Singleton01.getSingleton01();
// // 使用反射破解单例
// System.out.println(singleton02 == singleton03); false
}
}
}
如何防止反射单例被破解
- 不使用懒汉式(静态内部类除外)
- 反射创建单例置于 new获取单例对象之后
2、序列化破解单例
序列化:将对象转换成二进制的形式
反序列化:从硬盘/网络读取二进制变为对象
public class Singleton02 implements Serializable {
private static Singleton02 singleton = new Singleton02();
public static Singleton02 getSingleton() {
return singleton;
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 1.将对象序列化存入到本地文件中
FileOutputStream fos = new FileOutputStream("d:/code/a.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
Singleton02 singleton1 = Singleton02.getSingleton();
oos.writeObject(singleton1);
oos.close();
fos.close();
System.out.println("----------从硬盘中反序列化对象到内存中------------");
//2.从硬盘中反序列化对象到内存中
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/code/a.txt"));
// 从新获取一个新的对象
Singleton02 singleton2 = (Singleton02) ois.readObject();
System.out.println(singleton1 == singleton2);
}
//如何防御反序列化创建新的对象
//java层面 封装好了
// readObject 通过反射技术 序列化类中存在readResolve方法 保证单例
private Object readResolve() throws ObjectStreamException {
return singleton;
}
}
序列化类中存在readResolve方法 保证单例
原理:
1. 调用readObject()
2. 执行readObject0();
3. Switch 判断
![请添加图片描述](https://img-blog.csdnimg.cn/73177be3dcfa405fa919828f7991bfa0.png)
判断反序列化类中如果存在readResolve方法 则通过反射机制调用readResolve方法返回相同的对象