1,饿汉试
线程安全
public class ThreadPool { private static ThreadPool threadPool = new ThreadPool(); private ThreadPool() { } public static ThreadPool getInstance() { return threadPool; } }
2,懒汉式
线程不安全,加双锁
public class ThreadPool { private static ThreadPool threadPool; private ThreadPool() { } public static synchronized ThreadPool getInstance() { if (threadPool == null) { synchronized (ThreadPool.class) { if (threadPool == null) { threadPool = new ThreadPool(); } } } return threadPool; } }
3,枚举 枚举类型本身JVM 就会保证其是单例
public class SpringIOC { private SpringIOC() { } public static SpringIOC getInstance(){ return SingleTon.INSTANCE.getInstance(); } static enum SingleTon { INSTANCE; private SpringIOC springIOC; private SingleTon() { springIOC = new SpringIOC(); } public SpringIOC getInstance() { return this.springIOC; } } }
4,静态内部类
public class TaskManager { private TaskManager() { } private static class TaskManagerHolder { private static final TaskManager taskManager = new TaskManager(); } public static TaskManager getInstance() { return TaskManagerHolder.taskManager; } }
单例的优化,防止被外部攻击
通过反射能够攻击有些单例模式,生成新的对象,以饿汉式为例:
public class ThreadPool { private static boolean flag = false; private static ThreadPool threadPool = new ThreadPool();
//无参构造方法 private ThreadPool() { } public static ThreadPool getInstance() { return threadPool; } public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException { ThreadPool instance1 = ThreadPool.getInstance(); Class<?> clazz = Class.forName("com.hella.thread.pattern.ThreadPool"); ThreadPool instance2 = (ThreadPool) clazz.newInstance(); System.out.println(instance1 == instance2 ); //false 单例被攻击 } }
因为通过new 生成的关键字,通过调用public 的构造方法
通过反射 Class 对象下的newInstance() 方法是通过空的构造方法生成的对象,无论访问修饰符是public 或者 private
那若是带参数的构造方法,可以攻击吗? 也是可以的!
通过 getDeclaredConstructor 获取到构造方法,也是可以生成对象
public class ThreadPool { private String username; private static boolean flag = false; private static ThreadPool threadPool = new ThreadPool("Chris"); private ThreadPool(String username) { this.username = username; } public static ThreadPool getInstance() { return threadPool; } public static void main(String[] args) throws Exception { ThreadPool instance1 = ThreadPool.getInstance(); Class<?> clazz = Class.forName("com.hella.thread.pattern.ThreadPool"); Constructor<?> constructor = clazz.getDeclaredConstructor(String.class); ThreadPool instance2 = (ThreadPool) constructor.newInstance("Chris"); System.out.println(instance1 == instance2); //false 单例已经被攻击 } }
通过指定一个flag ,代表只能调用一次构造方法,来防止被攻击
public class ThreadPool { private String username; private static boolean flag = false; private static ThreadPool threadPool = new ThreadPool("Chris"); private ThreadPool(String username) { if (!flag) { this.username = username; flag = true; // 类加载的时候,jvm 会第一次调用生成静态对象,将flag 改为true,意思是只允许成功调用一次 } else { throw new RuntimeException("单例正在被攻击"); } } public static ThreadPool getInstance() { return threadPool; } public static void main(String[] args) throws Exception { ThreadPool instance1 = ThreadPool.getInstance(); Class<?> clazz = Class.forName("com.hella.thread.pattern.ThreadPool"); Constructor<?> constructor = clazz.getDeclaredConstructor(String.class); ThreadPool instance2 = (ThreadPool) constructor.newInstance("Chris"); System.out.println(instance1 == instance2); } }
那是不是代表这种防止被攻击就安全了呢?不是!
可以获取到flag 的值,再重新赋值,就可以通过反射继续生成对象了,从而破环单例。
public class ThreadPool { private String username; private static boolean flag = false; private static ThreadPool threadPool = new ThreadPool("Chris"); private ThreadPool(String username) { if (!flag) { this.username = username; flag = true; // 类加载的时候,jvm 会第一次调用生成静态对象,将flag 改为true,意思是只允许成功调用一次 } else { throw new RuntimeException("单例正在被攻击"); } } public static ThreadPool getInstance() { return threadPool; } public static void main(String[] args) throws Exception { ThreadPool instance1 = ThreadPool.getInstance(); Class<?> clazz = Class.forName("com.hella.thread.pattern.ThreadPool"); Field[] fields = clazz.getDeclaredFields(); for (int i = 0; i < fields.length; i++) {
// 私有属性必须要通过setAccessible(true)来访问 fields[i].setAccessible(true); if (fields[i].getName().equals("flag")) { fields[i].setBoolean(instance1, false); } } Constructor<?> constructor = clazz.getDeclaredConstructor(String.class); ThreadPool instance2 = (ThreadPool) constructor.newInstance("Chris"); System.out.println(instance1 == instance2); } }
静态内部类的防止攻击:
public class TaskManager { private TaskManager() { if (TaskManagerHolder.taskManager == null) { return; } throw new RuntimeException("单例正在被攻击"); } private static class TaskManagerHolder { private static final TaskManager taskManager = new TaskManager(); } public static TaskManager getInstance() { return TaskManagerHolder.taskManager; } public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException { TaskManager instance1 = TaskManager.getInstance(); Class<?> clazz = Class.forName("com.hella.thread.pattern.TaskManager"); TaskManager instance2 = (TaskManager) clazz.newInstance(); System.out.println(instance1 == instance2); } }
如何选择单例创建方式
如果不需要延迟加载单例,可以使用枚举或者饿汉式,相对来说枚举性好于饿汉式。
如果需要延迟加载,可以使用静态内部类或者懒韩式,相对来说静态内部类好于懒韩式。