1. 单例模式的适用场景
确保任何情况下都绝对只有一个实例
ServletContext,ServletConfig,ApplicationContext,DBPool…
2. 单例模式的常见写法
2.1 饿汉式单例
在单例类首次加载时就创建实例(两种写法)
HungrySingleton
public class HungrySingleton {private static final HungrySingleton hungrySingleton = new HungrySingleton();//构造方法私有private HungrySingleton(){}//对外提供返回实例public static HungrySingleton getInstance(){return hungrySingleton;}}
HungryStaticSingleton
public class HungryStaticSingleton {private static final HungryStaticSingleton hungrySingleton;static {hungrySingleton = new HungryStaticSingleton();}//构造方法私有private HungryStaticSingleton(){}//对外提供返回实例public static HungryStaticSingleton getInstance(){return hungrySingleton;}}
2.2 懒汉式单例
被外部调用时才创建实例
LazySimpleSingleton (线程不安全)
public class LazySimpleSingleton {private static LazySimpleSingleton lazy = null;//构造私有private LazySimpleSingleton(){}//创建对象public static LazySimpleSingleton getInstance(){if (null == lazy){lazy = new LazySimpleSingleton();}return lazy;}}
保证安全模式下加上
synchronized 关键字
public class LazySimpleSingleton {private static LazySimpleSingleton lazy = null;//构造私有private LazySimpleSingleton(){}//创建对象public synchronized static LazySimpleSingleton getInstance(){if (null == lazy){lazy = new LazySimpleSingleton();}return lazy;}}
LazyInnerClassSingleton 内部类方式单例 性能最优
//全程没有用到synchronized关键字//public class LazyInnerClassSingleton {private LazyInnerClassSingleton(){}//懒汉式单例//LazyHolder里面的逻辑需要外部方法调用时才执行//巧妙了使用了内部类的特性//JVM底层的实现逻辑//完美的避免了线程安全逻辑public static final LazyInnerClassSingleton getInstance(){return LazyHolder.LAZY;}private static class LazyHolder{private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();}}
构造方法私有,逃不过反射的法眼!!!
LazyInnerClassSingletonTest 测试类
public class LazyInnerClassSingletonTest {public static void main(String[] args) {Class<?> clazz = LazyInnerClassSingleton.class;try {Constructor<?> c = clazz.getDeclaredConstructor(null);c.setAccessible(true);//强制访问Object o1 = c.newInstance();Object o2 = LazyInnerClassSingleton.getInstance();System.out.println(o1 == o2);} catch (Exception e) {e.printStackTrace();}}}
可以发现o1 == o2为false,所以需要在new对象的时候进行条件判断!
private LazyInnerClassSingleton(){if (LazyHolder.LAZY != null){throw new RuntimeException("不允许构建多个实例!");}}
SeriableSingleton 序列化破解方式
public class SeriableSingleton implements Serializable {//反序列化//将已经序列化的字节码内容,转化为IO流//通过IO的读取,进而将读取的内容转换为java对象//在转换过程中会重新创建对象newpublic static final SeriableSingleton INSTANCE = new SeriableSingleton();private SeriableSingleton(){}public static SeriableSingleton getInstance(){return INSTANCE;}//解决实例化对象不一致的问题 重写方法//重写了readResolve方法,只不过是覆盖了反序列化出来的对象//还是创建了两次 发生在JVM层面 相对来说 比较安全//之前反序列化出来的对象会被GC回收private Object readResolve(){return INSTANCE;}}
SeriableSingletonTest 测试类
public class SeriableSingletonTest {public static void main(String[] args) {SeriableSingleton s1 = null;SeriableSingleton s2 = SeriableSingleton.getInstance();FileOutputStream fos = null;try {fos = new FileOutputStream("SeriableSingleton.obj");ObjectOutputStream oos = new ObjectOutputStream(fos);oos.writeObject(s2);oos.flush();oos.close();FileInputStream fis = new FileInputStream("SeriableSingleton.obj");ObjectInputStream ois = new ObjectInputStream(fis);s1 = (SeriableSingleton) ois.readObject();ois.close();System.out.println(s1);System.out.println(s2);System.out.println(s1 == s2);}catch (Exception e){e.printStackTrace();}}}

源码实例化会去寻找resolve方法
2.3 注册式单例
将每一个实例都缓存到统一的容器中,使用唯一标识获取实例
枚举式单例可以避免反序列化多实例的问题!!!
EnumSingleton 枚举式单例
public enum EnumSingleton {INSTACNE;private Object data;public Object getData() {return data;}public void setData(Object data) {this.data = data;}public static EnumSingleton getInstance(){ return INSTACNE; }}
EnumSingletonTest 反序列化测试
public class EnumSingletonTest {public static void main(String[] args) {EnumSingleton s1 = null;EnumSingleton s2 = EnumSingleton.getInstance();s2.setData(new Object());FileOutputStream fos = null;try {fos = new FileOutputStream("EnumSingleton.obj");ObjectOutputStream oos = new ObjectOutputStream(fos);oos.writeObject(s2);oos.flush();oos.close();FileInputStream fis = new FileInputStream("EnumSingleton.obj");ObjectInputStream ois = new ObjectInputStream(fis);s1 = (EnumSingleton) ois.readObject();ois.close();System.out.println(s1);System.out.println(s2);System.out.println(s1.getData() == s2.getData());}catch (Exception e){e.printStackTrace();}}}
输出结果s1==s2 为 true,避免了反序列化的多实例!!!
这里给大家展示一个上述反编译之后的代码EnumSingleton
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.// Jad home page: http://www.kpdus.com/jad.html// Decompiler options: packimports(3)// Source File Name: EnumSingleton.javapackage com.tiansu.shejimoshi.singleton.register;public final class EnumSingleton extends Enum{public static EnumSingleton[] values(){return (EnumSingleton[])$VALUES.clone();}public static EnumSingleton valueOf(String name){return (EnumSingleton)Enum.valueOf(com/tiansu/shejimoshi/singleton/register/EnumSingleton, name);}private EnumSingleton(String s, int i){super(s, i);}public Object getData(){return data;}public void setData(Object data){this.data = data;}public static EnumSingleton getInstance(){return INSTACNE;}public static final EnumSingleton INSTACNE;private Object data;private static final EnumSingleton $VALUES[];static{INSTACNE = new EnumSingleton("INSTACNE", 0);$VALUES = (new EnumSingleton[] {INSTACNE});}}
这里IO在反序列化的时候读取的对象源码
源码中使用一个类名和枚举名确定一个枚举值!!!
ContainerSingleton 容器式注册单例
public class ContainerSingleton {private ContainerSingleton(){}private static Map<String, Object> ioc = new ConcurrentHashMap<>();public static Object getBean(String className){if (!ioc.containsKey(className)){Object obj = null;try {obj = Class.forName(className).newInstance();ioc.put(className, obj);return obj;}catch (Exception e){e.printStackTrace();}}return ioc.get(className);}}
ConcurrentExecutor 线程执行类
public class ConcurrentExecutor {/*** @param runHandler* @param executeCount 发起请求总数* @param concurrentCount 同时并发执行的线程数* @throws Exception*/public static void execute(final RunHandler runHandler,int executeCount,int concurrentCount) throws Exception {ExecutorService executorService = Executors.newCachedThreadPool();//控制信号量,此处用于控制并发的线程数final Semaphore semaphore = new Semaphore(concurrentCount);//闭锁,可实现计数量递减final CountDownLatch countDownLatch = new CountDownLatch(executeCount);for (int i = 0; i < executeCount; i ++){executorService.execute(new Runnable() {public void run() {try{//执行此方法用于获取执行许可,当总计未释放的许可数不超过executeCount时,//则允许同性,否则线程阻塞等待,知道获取到许可semaphore.acquire();runHandler.handler();//释放许可semaphore.release();}catch (Exception e){e.printStackTrace();}countDownLatch.countDown();}});}countDownLatch.await();//线程阻塞,知道闭锁值为0时,阻塞才释放,继续往下执行executorService.shutdown();}public interface RunHandler{void handler();}}
ContainerSingleTest 测试类
public class ContainerSingleTest {public static void main(String[] args) {try {ConcurrentExecutor.execute(new ConcurrentExecutor.RunHandler() {@Overridepublic void handler() {Object bean = ContainerSingleton.getBean("com.tiansu.shejimoshi.template.jdbc.Member");System.out.println(bean);}},10,6);//对象方便管理 属于懒加载//线程不安全}catch (Exception e){e.printStackTrace();}}}
测试结果
测试结果反应了线程不安全的情况出现,解决办法,可以给实例方法加上关键字syncronized
2.4 ThreadLocal单例
ThreadLocalSingleton
//伪线程安全public class ThreadLocalSingleton {private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance =new ThreadLocal<ThreadLocalSingleton>(){@Overrideprotected ThreadLocalSingleton initialValue() {return new ThreadLocalSingleton();}};public static ThreadLocalSingleton getInstance(){return threadLocalInstance.get();}}
ExectorThread 线程执行类
public class ExectorThread implements Runnable{@Overridepublic void run() {//LazySimpleSingleton singleton = LazySimpleSingleton.getInstance();ThreadLocalSingleton singleton = ThreadLocalSingleton.getInstance();System.out.println(Thread.currentThread().getName() + ":" + singleton);}}
ThreadLocalSingletonTest 测试类
public class ThreadLocalSingletonTest {public static void main(String[] args) {System.out.println(ThreadLocalSingleton.getInstance());System.out.println(ThreadLocalSingleton.getInstance());System.out.println(ThreadLocalSingleton.getInstance());System.out.println(ThreadLocalSingleton.getInstance());System.out.println(ThreadLocalSingleton.getInstance());System.out.println(ThreadLocalSingleton.getInstance());Thread t1 = new Thread(new ExectorThread());Thread t2 = new Thread(new ExectorThread());t1.start();t2.start();}}

线程注册式单例,对于线程内部每次调用只会产生一个对象,保证线程内部的全局唯一,且天生线程安全,底层使用的是当前线程作为key和new出对象作为value的map方式进行存储并且获取。
3. 单例模式的优缺点
优点:
在内存中只有一个实例,减少了内存开销。
可以避免对资源的多重占用。
设置全局访问点,严格控制访问。
缺点:
没有接口,扩展困难。
如果要扩展单例对象,只有修改代码,没有其他途径。
4. 学习单例的重点总结
1)、私有化构造器
2)、保证线程安全
3)、延迟加载
4)、防止序列化和反序列化破坏单例
5)、防御反射攻击单例
