单例模式
保证该类只有一个实例,收回了实例的创建权限,通过一个对外接口提供实例对象。
使用场景
当创建一个对象需要消耗很多资源,或设计要求系统中该类只能有一个实例时使用该模式。
实现要点
- 构造函数私有化
- 通过静态方法或枚举返回单例对象,单例类中使用私有静态引用持有本对象
- 确保在获取对象时是线程安全的
- 确保反序列化时不会重新构建该对象
实践
1.饿汉模式
实现简单,但不能做到延迟加载
public class SingletonPattern1 implements Serializable {
private static SingletonPattern1 instance = new SingletonPattern1();
private SingletonPattern1() {
// 防止反射获取多个对象的漏洞。
if (null != instance) {
throw new RuntimeException();
}
}
public static SingletonPattern1 getInstance() {
return instance;
}
// 防止反序列化导致多个对象的漏洞
private Object readResolve() throws ObjectStreamException {
return instance;
}
}
2.懒汉模式
优化了饿汉不能延迟加载的问题,在获取对象时先判空再实例化。但在多线程下单例失效。
public class SingletonPattern2 implements Serializable {
private static SingletonPattern2 instance;
private SingletonPattern2() {
if (null != instance) {
throw new RuntimeException();
}
}
public static SingletonPattern2 getInstance() {
if (null == instance) {
instance = new SingletonPattern2();
}
return instance;
}
private Object readResolve() throws ObjectStreamException {
return instance;
}
}
3.同步懒汉模式
在判空实例化时进行同步,优化了普通懒汉在多线程下失效的问题。但每次获取不论实例是否为空都会进行同步操作,导致性能较低。
public class SingletonPattern3 implements Serializable {
private static SingletonPattern3 instance;
private SingletonPattern3() {
if (null != instance) {
throw new RuntimeException();
}
}
/**
* 对方法进行同步,解决多线程下实例不唯一的问题。
*
* @return
*/
public synchronized SingletonPattern3 getInstance() {
if (null == instance) {
instance = new SingletonPattern3();
}
return instance;
}
/**
* 使用同步代码块
*
* @return
*/
public SingletonPattern3 getInstance1() {
synchronized (SingletonPattern3.class) {
if (null == instance) {
instance = new SingletonPattern3();
}
return instance;
}
}
private Object readResolve() throws ObjectStreamException {
return instance;
}
}
4.DCL 两步检查同步懒汉
使用两步检查解决了同步懒汉在实例不为空时仍需要同步的问题,使用violate避免了因JVM在对象实例化(new Obj 不是原子操作,存在优化重排序)过程中指令重排序导致模式失效的问题。
/**
* DCl double check lock
* 双检查锁机制,解决了在instance不为空时也要同步的缺陷,
* 但是 new SingletonPattern4(); 不是原子操作,大致分为以下三个操作。
* (1)在堆中划分内存
* (2)调用构造函数进行初始化
* (3)instance指向分配的内存空间
* 正常是123,但是java 存在指令的重排序,导致出现132的情况,13完成时 另一个线程读取该对象时,就会导致获得一个未初始化的对象
*
* 使用volatile 就会避免该问题
*/
public class SingletonPattern4 implements Serializable {
private static volatile SingletonPattern4 instance;
private SingletonPattern4() {
if (null != instance) {
throw new RuntimeException();
}
}
public static SingletonPattern4 getInstance() {
if (null == instance) {
synchronized (SingletonPattern4.class) {
if (null == instance) {
instance = new SingletonPattern4();
}
}
}
return instance;
}
private Object readResolve() throws ObjectStreamException {
return instance;
}
}
5.静态内部类模式
在私有的静态内部类内部中持有外部单例类的实例对象,获取实例对象时返回内部类中实例对象。没有使用synchronize,提高了性能,在第一次调用get方法时才会加载静态内部类,线程安全且全局唯一,也实现了延迟加载。
public class SingletonPattern5 implements Serializable {
public static SingletonPattern5 getInstance() {
return InstanceHolder.instance;
}
private SingletonPattern5() {
if (null != InstanceHolder.instance) {
throw new RuntimeException();
}
}
private static class InstanceHolder {
private static final SingletonPattern5 instance = new SingletonPattern5();
}
private Object readResolve() throws ObjectStreamException {
return InstanceHolder.instance;
}
}
6.枚举单例
枚举在java中和class一样,可以有字段也可以有方法,线程安全且能保持唯一。只有序列化存入磁盘,从磁盘反序列化得到新实例的时候会破坏唯一性。
public enum SingletonPattern6 {
INSTANCE;
public void doSomething(){
System.out.println("do sth.");
}
}
// 调用
public static void main(String[] args) {
SingletonPattern6.INSTANCE.doSomething();
}
7.使用容器实现单例
public class SingletonPattern7 {
private static Map<String, Object> objectMap = new HashMap<>();
private SingletonPattern7() {
}
public static void registerService(String key, Object instance) {
if (!objectMap.containsKey(key)) {
objectMap.put(key, new SingletonPattern7());
}
}
public static Object getService(String key) {
return objectMap.get(key);
}
}
以上单例模式其实在反序列化的时候都会破坏唯一性,因为反序列化时会通过一个特殊途径创建对象实例,相当于调用了私有构造函数。
可以通过加入readResolve方法,让开发人员控制对象的反序列化。
private Object readResolve() throws ObjectStreamException {
return instance;
}
4. 总结
(建议用单例模式创建okHttpClient)OkHttpClient, 可以通过 new OkHttpClient() 或 new OkHttpClient.Builder() 来创建对象, 但是—特别注意, OkHttpClient() 对象最好是共享的, 建议使用单例模式创建。 因为每个 OkHttpClient 对象都管理自己独有的线程池和连接池。 这一点很多同学,甚至在我经历的团队中就有人踩过坑, 每一个请求都创建一个 OkHttpClient 导致内存爆掉
本文详细介绍了Java中的单例模式实现方式,包括饿汉模式、懒汉模式、双重检查锁定模式、静态内部类模式和枚举单例模式。强调了线程安全、延迟加载和反序列化问题,并提供了防止反序列化破坏单例的解决方案。同时,文章指出在创建如OkHttpClient等对象时,应使用单例模式以避免资源浪费,因为每个实例都管理自己的线程池和连接池。
1272

被折叠的 条评论
为什么被折叠?



