设计模式4 - 单例模式 Singleton Pattern
Posted hlkawa
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式4 - 单例模式 Singleton Pattern相关的知识,希望对你有一定的参考价值。
单例 :单例模式确保一个类只有一个实例,并提供全局访问点,实现单例模式的方法是私有化构造函数,通过getInstance()方法实例化对象,并返回这个实例,并保证在JVM中只有一个实例
单例模式优缺点
优点
1、单例类只有一个实例,不会频繁创建对象
2、共享资源,全局使用,访问速度比较快(只有一个实例服用,不用频繁的创建)
3、节省创建时间,提高性能
缺点
1、因为只有一个实例对象,多线程情况下,会出现线程安全问题
2、单例实例化的对象,存放在方法区(java8 叫元空间)不会被回收,大量单例容易出现内存溢出问题(比如配置文件过大,实例化出来的类导致内存溢出)
单例模式应用场景
配置文件,Spring IOC容器默认单例,线程池和数据库连接池(复用机制),Servlet(线程不安全),JVM内置缓存Oscache,Ehcache (HashMap + 淘汰策略),枚举类,常量,计数器
单例的七钟写法
饿汉、懒汉(非线程安全)、懒汉(线程安全)、双重校验锁、静态内部类、枚举和容器类管理
饿汉式
* 饿汉式单例 就初始化一次 * 优点;先天线程安全 => 当类被加载是就创建了实例 * 缺点:static修饰的占用方法区,定义太多即使不使用也会占用内存,项目启动也会变慢 **/ public class Singleton饿汉式1 implements Serializable { private static Singleton饿汉式1 singleton饿汉式 = new Singleton饿汉式1(); //无参构造函数 private private Singleton饿汉式1() { } public static Singleton饿汉式1 getSingleton饿汉式() { return singleton饿汉式; } public static void main(String[] args) { Singleton饿汉式1 singleton饿汉式1 = Singleton饿汉式1.getSingleton饿汉式(); Singleton饿汉式1 singleton饿汉式2 = Singleton饿汉式1.getSingleton饿汉式(); System.out.println(singleton饿汉式1 == singleton饿汉式2); }
懒汉式(线程安全)
如果不加锁就是线程不安全
* 在被调用的时候才创建 * 缺点: 线程不安全 * 加锁synchronized 可以保证线程安全,但一般加synchronized锁意味着有线程等待 **/ public class Singleton懒汉式2 { private static Singleton懒汉式2 singleton懒汉式; //构造函数私有化 private Singleton懒汉式2() { //防止java反射技术破解 if(singleton懒汉式 != null){ try { throw new Exception("该对象已经被实例化。。。。"); } catch (Exception e) { e.printStackTrace(); } } } public synchronized static Singleton懒汉式2 getSingleton懒汉式() { if(singleton懒汉式 == null){ try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } singleton懒汉式 = new Singleton懒汉式2(); return singleton懒汉式; } return singleton懒汉式; } public static void main(String[] args) { /* Singleton懒汉式 singleton懒汉式1 = Singleton懒汉式.getSingleton懒汉式(); Singleton懒汉式 singleton懒汉式2 = Singleton懒汉式.getSingleton懒汉式(); System.out.println(singleton懒汉式1 == singleton懒汉式2);*/ for (int i = 0; i < 20; i++) { new Thread(new Runnable() { @Override public void run() { Singleton懒汉式2 singleton懒汉式1 = Singleton懒汉式2.getSingleton懒汉式(); System.out.println(Thread.currentThread().getName() + singleton懒汉式1); } }).start(); } } }
双重检验锁(DCL)
* 双重检验锁相比懒汉式,在实例化类的时候才加锁在读的时候不加锁,相比懒汉式减少了线程在读时候等待的问题 * 但是在实例化的过程加了锁还是存在线程等待的问题 * **/ public class Singleton双重检验锁3 { private volatile static Singleton双重检验锁3 singleton双重检验锁3; //构造函数私有化 private Singleton双重检验锁3() { } public static Singleton双重检验锁3 getSingleton双重检验锁3() { if(singleton双重检验锁3 == null){ try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (Singleton双重检验锁3.class){ if(singleton双重检验锁3 == null){ singleton双重检验锁3 = new Singleton双重检验锁3(); } } } return singleton双重检验锁3; } public static void main(String[] args) { for (int i = 0; i < 20; i++) { new Thread(new Runnable() { @Override public void run() { Singleton双重检验锁3 双重检验锁 = Singleton双重检验锁3.getSingleton双重检验锁3(); System.out.println(Thread.currentThread().getName() + 双重检验锁); } }).start(); } } }
静态内部内形式
* 继承了懒汉式和饿汉式的优点 * 内部类在被调用的时候才会初始化对象 **/ public class Singleton静态内部类5 { private Singleton静态内部类5() { System.out.println("构造函数初始化。。。。"); } public static class SingletonUtil{ private static Singleton静态内部类5 singleton静态内部类 = new Singleton静态内部类5(); } public static void main(String[] args) { System.out.println("main方法启动。。。。"); Singleton静态内部类5 singleton静态内部类1 = SingletonUtil.singleton静态内部类; Singleton静态内部类5 singleton静态内部类2 = SingletonUtil.singleton静态内部类; System.out.println(singleton静态内部类1 == singleton静态内部类2); } }
枚举形式
枚举形式能够先天性,防止反射和序列化破解单例。
public enum Singleton枚举单例8 { SINGLETON_枚举单例; private String name; public void setName(String name) { this.name = name; } public String getName() { System.out.println("----getName----: " +name); return name; } public static void main(String[] args) throws Exception { Singleton枚举单例8 枚举单例1 = Singleton枚举单例8.SINGLETON_枚举单例; Singleton枚举单例8 枚举单例2 = Singleton枚举单例8.SINGLETON_枚举单例; 枚举单例1.setName("kawa"); 枚举单例2.getName(); System.out.println(枚举单例1 == 枚举单例2); //序列化破解发现获取的是同一个对象 //序列化 Singleton枚举单例8 枚举单例4 = Singleton枚举单例8.SINGLETON_枚举单例; FileOutputStream fos = new FileOutputStream(System.getProperty("user.dir")+"\Singleton2.txt"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(枚举单例4); oos.flush(); oos.close(); //反序列化 FileInputStream fis = new FileInputStream(System.getProperty("user.dir")+"\Singleton2.txt"); ObjectInputStream ois = new ObjectInputStream(fis); Singleton枚举单例8 枚举单例5 = (Singleton枚举单例8) ois.readObject(); System.out.println(枚举单例4==枚举单例5); //反射机制破解 会直接抛出异常 NoSuchMethodException Constructor<Singleton枚举单例8> constructor = Singleton枚举单例8.class.getDeclaredConstructor(); constructor.setAccessible(true); Singleton枚举单例8 枚举单例3 = constructor.newInstance(); System.out.println(枚举单例3 == 枚举单例2); }
使用容器管理
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); } } }
如何防止破坏单例这种使用SingletonManager 将多种单例类统一管理,在使用时根据key获取对象对应类型的对象。这种方式使得我们可以管理多种类型的单例,并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。
最后额外补充下,对象创建的方式:
1. 通过 new 创建
2. 通过反射 (如:Class.forName() )创建
3. 通过序列化创建
4. 通过clone()方式创建
以上是关于设计模式4 - 单例模式 Singleton Pattern的主要内容,如果未能解决你的问题,请参考以下文章