什么是单例模式
保证系统运行中,应⽤该模式的类只有⼀个对象实例
全局唯一
使⽤场景
业务系统全局只需要⼀个对象实例,⽐如发号器
redis连接对象
Spring IOC容器中的bean默认就是单例
spring @autowire的依赖注⼊对象默认都是单例的
如何写一个单例模式
1.将构造函数私有化
2.通过静态方法获取一个唯一实例
3.保证线程安全
4.防止反序列化造成的新实例等
饿汉式
// 模仿着Runtime实现
/**
* 饿汉 : 声明静态时已经初始化,在获取对象之前就初始化
*/
public class SingleHungerPattern {
// 3.声明静态时已经初始化,在获取对象之前就初始化,没有线程安全问题
private static SingleHungerPattern instance = new SingleHungerPattern();
// 2.通过静态方法获取一个唯一实例
public static SingleHungerPattern getInstance(){
return instance;
}
// 1.私有构造函数
private SingleHungerPattern(){
System.out.println("SingleHungerPattern实例化");
}
public static void main(String[] args) {
SingleHungerPattern instance1 = SingleHungerPattern.getInstance();
SingleHungerPattern instance2 = SingleHungerPattern.getInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
懒汉式
DCL 双重检查锁
/**
* 懒汉 就是所谓的懒加载,延迟创建对象
* 最多使用 : DCL 双重检查锁
*/
public class SingleLazyPattern {
/**
* 3. 使用 volatile 保证线程安全
* 禁止指令重排
* 保证可见性
* 不保证原子性
*/
private static volatile SingleLazyPattern instance;
// 1.私有构造函数
private SingleLazyPattern(){
System.out.println("SingleLazyPattern实例化了");
}
/**
* 2.通过静态方法获取一个唯一实例
* DCL 双重检查锁定 (Double-CheckedLocking)
* 在多线程情况下保持⾼性能
*/
public static SingleLazyPattern getInstance(){
if(instance == null){
synchronized (SingleLazyPattern.class){
if(instance == null){
// 1. 分配内存空间 2、执行构造方法,初始化对象 3、把这个对象指向这个空间
instance = new SingleLazyPattern();
}
}
}
return instance;
}
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(()->{
SingleLazyPattern.getInstance();
}).start();
}
}
}
静态内部类
/**
* 静态内部类
*/
public class StaticInnerClass {
// 1.私有构造函数
private StaticInnerClass(){
System.out.println("StaticInnerClass实例化");
}
// 2.通过静态方法获取一个唯一实例
public static StaticInnerClass getInstance(){
return innerClass.instance;
}
// 3.保证线程安全 调用 StaticInnerClass.instance 的时候,才会对单例进行初始化
private static class innerClass{
private final static StaticInnerClass instance = new StaticInnerClass();
}
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(()->{
StaticInnerClass.getInstance();
}).start();
}
}
}
使用枚举
枚举也是类,并且还继承了java.lang.Enum。
由汇编可以看出,枚举的初始化过程是在静态代码块中进行,
我们知道,静态代码块只在类首次加载的时候执行一次,final域保证引用地址不会改变。
同时,我们也没有看到有公有的构造方法,客户端也就不能创建新的实例,
由此可见,枚举类型具有实例受限的特性。
解决单例的并发问题,主要解决的就是初始化过程中的线程安全问题。
所以,由于枚举的以上特性,枚举实现的单例是天生线程安全的。
/**
* 使用枚举获取单例
*/
public enum SingleEnum {
Instance;
SingleEnum(){
System.out.println("SingleEnum实例化了");
}
public static SingleEnum getInstance(){
return Instance;
}
public static void main(String[] args) {
SingleEnum instance1 = SingleEnum.getInstance();
SingleEnum instance2 = SingleEnum.getInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
java中有几种可以创建对象的方式
1、通过new语句实例化一个对象
2、通过反射机制
3、通过clone()创建对象
4、通过反序列化的方式创建对象
思考 那么以上的四种创建对象的方式能不能破坏单例模式呢
构造器私有已经无法通过new 创建对象了
序列化会破坏单例模式
链接: 单例、序列化和readResolve()方法.
链接: Java 之 Serializable 序列化和反序列化的概念,作用的通俗易懂的解释.
import java.io.*;
public class SingleHungerPattern implements Serializable {
// 3.声明静态时已经初始化,在获取对象之前就初始化,没有线程安全问题
private static SingleHungerPattern instance = new SingleHungerPattern();
// 2.通过静态方法获取一个唯一实例
public static SingleHungerPattern getInstance(){
return instance;
}
// 1.私有构造函数
private SingleHungerPattern(){
System.out.println("SingleHungerPattern实例化");
}
public static void main(String[] args) throws Exception {
SingleHungerPattern instance1 = SingleHungerPattern.getInstance();
System.out.println(instance1);
serialize(instance1);
SingleHungerPattern instance2 = deserialize();
System.out.println(instance2);
}
/**
* 序列化
*/
private static void serialize(SingleHungerPattern instance) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("d:/flyPig.txt")));
oos.writeObject(instance);
oos.close();
System.out.println("对象序列化成功!");
}
/**
* 反序列化
*/
private static SingleHungerPattern deserialize() throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("d:/flyPig.txt")));
SingleHungerPattern instance = (SingleHungerPattern) ois.readObject();
System.out.println("对象反序列化成功!");
return instance;
}
}
如何防止序列化破坏单例模式
/**
*若目标类有readResolve方法,那就通过反射的方式调用要被反序列化的类中的readResolve方法,返回一个
*对象,然后把这个新的对象复制给之前创建的obj(即最终返回的对象)。那被反序列化的类中的
* readResolve 方法里是什么?就是直接返回我们的单例对象。
* 防止序列化造成的破坏
* @return
*/
private Object readResolve() {
return instance;
}
克隆也会破坏单例模式
public class SingleHungerPattern implements Cloneable{
// 3.声明静态时已经初始化,在获取对象之前就初始化,没有线程安全问题
private static SingleHungerPattern instance = new SingleHungerPattern();
// 2.通过静态方法获取一个唯一实例
public static SingleHungerPattern getInstance(){
return instance;
}
// 1.私有构造函数
private SingleHungerPattern(){
System.out.println("SingleHungerPattern实例化");
}
public static void main(String[] args) throws Exception {
SingleHungerPattern instance1 = SingleHungerPattern.getInstance();
System.out.println(instance1);
SingleHungerPattern instance2 = (SingleHungerPattern)instance1.clone();
System.out.println(instance2);
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();//单例实现了克隆接口 可以利用反射破坏单例
//return instance;//解决办法 即使通过反射调用clone方法 获取到的依然是原来的实例对象
}
}
分别对以上四种方式进行反射
饿汉- 被破坏
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Test {
public static void main(String[] args) throws NoSuchMethodException,
IllegalAccessException, InvocationTargetException, InstantiationException {
SingleHungerPattern instance1 = SingleHungerPattern.getInstance();
Constructor<SingleHungerPattern> declaredConstructor = SingleHungerPattern.class.
getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
SingleHungerPattern instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
DCL 双重检查锁- 被破坏
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Test {
public static void main(String[] args) throws NoSuchMethodException,
IllegalAccessException, InvocationTargetException, InstantiationException {
SingleLazyPattern instance1 = SingleLazyPattern.getInstance();
Constructor<SingleLazyPattern> declaredConstructor = SingleLazyPattern.class.
getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
SingleLazyPattern instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
静态内部类- 被破坏
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Test {
public static void main(String[] args) throws NoSuchMethodException,
IllegalAccessException, InvocationTargetException, InstantiationException {
StaticInnerClass instance1 = StaticInnerClass.getInstance();
Constructor<StaticInnerClass> declaredConstructor = StaticInnerClass.class.
getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
StaticInnerClass instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
枚举 – 不会破坏
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Test {
public static void main(String[] args) throws NoSuchMethodException,
IllegalAccessException, InvocationTargetException, InstantiationException {
SingleEnum instance1 = SingleEnum.getInstance();
Constructor<SingleEnum> declaredConstructor = SingleEnum.class.
getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
SingleEnum instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
Effective java 书中
链接: 为什么要用枚举实现单例模式(避免反射、序列化问题).
第89条:对于实例控制,枚举类型优先于readResolve
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/133938.html