设计模式-11 单例模式
文章目录1.什么是单例模式2.单例模式优缺点2.1 优点2.2 缺点3.单例写法3.1 饿汉式3.2 懒汉式(线程不安全)3.3 懒汉式(线程安全)3.4 双重检验锁3.5 静态内部类3.6 枚举方式3.7 容器管理4.如何防止破坏单例4.1 反射技术4.2 使用序列化技术破解单例5.防止破解5.1 反射技术5.2 序列化1.什么是单例模式单例模式确保一个类只有一个实例,并提供一个全局访问点...
·
文章目录
1.什么是单例模式
单例模式确保一个类只有一个实例,并提供一个全局访问点,实现单例模式的方法是私有化构造函数,通过getInstance()方法实例化对象,并返回这个实例
2.单例模式优缺点
2.1 优点
- 单例类只有一个实例
- 共享资源,全局使用
- 节省创建时间,提高性能
2.2 缺点
可能存在线程不安全的问题
3.单例写法
- 饿汉
- 懒汉(非线程安全)
- 懒汉(线程安全)
- 双重校验锁
- 静态内部类
- 枚举
- 容器类管理
- 静态块初始化
3.1 饿汉式
- 优点:先天性线程安全 为什么先天性 当类加载的时候 就被创建该对象 。
- 缺点:如果项目使用过多的饿汉式会发生问题,项目在启动的时候会边的非常慢、存放在方法区占用内存比较大
public class SingletonV1 {
private static SingletonV1 singletonV1 = new SingletonV1();
//1.单例模式是否可以让程序员初始化
private SingletonV1() {
}
/**
* 返回该对象的实例
*
* @return
*/
public static SingletonV1 getInstance() {
return singletonV1;
}
}
3.2 懒汉式(线程不安全)
public class SingletonV2 {
// 懒汉式 当真正需要使用该对象的时候才会被初始化 线程安全问题 但是效率非常低
private static SingletonV2 singletonV2;
//1.单例模式是否可以让程序猿初始化
private SingletonV2() {
}
/**
* 线程安全问题 在多线程情况下 可能会被初始化多次
* @return
*/
public static SingletonV2 getSingletonV2() {
//当第一次singletonV2 等于null 情况 才会被初始化
try {
Thread.sleep(3000);
} catch (Exception e) {
}
if (singletonV2 == null) {
singletonV2 = new SingletonV2();
}
return singletonV2;
}
}
- 测试线程安全性
public class SingletonV2Test {
public static void main(String[] args) {
// 如何去模拟高并发情况下 懒汉式线程安全问题
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
public void run() {
SingletonV2 singletonV1 = SingletonV2.getSingletonV2();
System.out.println(Thread.currentThread().getName() + "," + singletonV1);
}
}).start();
}
}
}
3.3 懒汉式(线程安全)
加入synchronized关键字
public synchronized static SingletonSafeV2 getSingletonV2() {
//当第一次singletonV2 等于null 情况 才会被初始化
try {
Thread.sleep(3000);
} catch (Exception e) {
}
if (singletonV2 == null) {
singletonV2 = new SingletonSafeV2();
}
return singletonV2;
}
3.4 双重检验锁
双重检验锁 解决懒汉式 读和写都加上锁的问题 缺点 第一次创建对象可能会比较慢
public class SingletonV3 {
/**
* volatile 防止重排序 java内存模型 增加可见性
*/
private volatile static SingletonV3 singletonV3;
// 双重检验锁 解决懒汉式 读和写都加上锁的问题 缺点 第一次创建对象可能会比较慢
// 如何解决 写和读 都不加锁 还能够保证唯一性 线程安全问题
private SingletonV3() throws Exception {
if (singletonV3 != null) {
throw new Exception("对象已经被初始化..");
}
System.out.println("SingletonV3被初始化...");
}
// /**
// * 读的不加锁的,写的时候才会加锁。。
// */
public static SingletonV3 getSingletonV3() throws Exception {
// 当多个线程 同时在可能new 对象的时候 才会加锁,保证线程问题。
if (singletonV3 == null) {
try {
Thread.sleep(3000);
} catch (Exception e) {
}
synchronized (SingletonV3.class) {
if (singletonV3 == null) { // 当前线程已经获取到锁的呢,在判断一下该对象是否已经初始化过,没有初始化过的 创建
singletonV3 = new SingletonV3();
}
}
}
return singletonV3;
}
// 双重检验锁目的什么? 解决懒汉式获取对象效率问题。
/**
* 如果 if singletonV3 == null 比较巧 正好有10个线程进入到呢 25行代码获取锁
* 因为synchronized 保证线程问题,只需要有一个线程获取锁,
*/
}
3.5 静态内部类
public class SingletonV4 {
private SingletonV4() {
System.out.println("构造函数被初始化...");
}
public static SingletonV4 getInstance() {
return SingletonV5Utils.singletonV4;
}
// 在类里面嵌套的
private static class SingletonV5Utils {
private static final SingletonV4 singletonV4 = new SingletonV4();
}
/**
* 内部类在调用的时候才会初始化singletonV5
* static 静态 保证唯一
* @param args
*/
// 静态内部类特征:继承懒汉式和饿汉式优点、同时解决双重检验锁第一次加载慢的问题 读和写都不需要同步效率非常高...
public static void main(String[] args) {
System.out.println("项目启动成功...");
SingletonV4 instance1 = SingletonV4.getInstance();
SingletonV4 instance2 = SingletonV4.getInstance();
System.out.println(instance1 == instance2);
}
}
3.6 枚举方式
public enum EnumSingleton {
INSTANCE;
// 枚举能够绝对有效的防止实例化多次,和防止反射和序列化破解
public void add() {
System.out.println("add方法...");
}
// 枚举是如何初始化的? 反序列化底层是如何解决防止单例被破解。
}
public class EnumSingletonTest {
public static void main(String[] args) {
EnumSingleton instance1 = EnumSingleton.INSTANCE;
EnumSingleton instance2 = EnumSingleton.INSTANCE;
System.out.println(instance1 == instance2);
instance1.add();
}
}
3.7 容器管理
这种使用SingletonManager 将多种单例类统一管理,在使用时根据key获取对象对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。
public class SingletonManager {
private static Map<String, Object> objMap = new HashMap<String, Object>();
public static void registerService(String key, Object instance) {
if (!objMap.containsKey(key)) {
objMap.put(key, instance);
}
}
public static Object getService(String key) {
{
return objMap.get(key);
}
}
}
4.如何防止破坏单例
4.1 反射技术
虽然单例通过私有构造函数,可以实现防止程序员初始化对象,但是还可以通过反射和序列化技术破解单例。
// 1. 使用懒汉式创建对象
SingletonV3 instance1 = SingletonV3.getInstance();
// 2. 使用Java反射技术初始化对象 执行无参构造函数
Constructor<SingletonV3> declaredConstructor = SingletonV3.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
SingletonV3 instance2 = declaredConstructor.newInstance();
System.out.println(instance1 == instance2);
4.2 使用序列化技术破解单例
Singleton instance = Singleton.getInstance();
FileOutputStream fos = new FileOutputStream("E:\\code\\Singleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(instance);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("E:\\code\\Singleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
Singleton singleton2 = (Singleton) ois.readObject();
System.out.println(singleton2==instance)
5.防止破解
5.1 反射技术
private SingletonV3() throws Exception {
synchronized (SingletonV3.class) {
if (singletonV3 != null) {
throw new Exception("该对象已经初始化..");
}
System.out.println("执行SingletonV3无参构造函数...");
}
}
5.2 序列化
//返回序列化获取对象 ,保证为单例
public Object readResolve() {
return singletonV3;
}
更多推荐
已为社区贡献3条内容
所有评论(0)