单例模式(Singleton Pattern)是指确保一个类在任何状况下都绝对只有一个实例,并提供一个全局访问点 。缓存
特色:隐藏其全部的构造方法安全
属于建立型模式性能优化
适用场景: 确保任何状况下都绝对只有一个实例网络
单例类:ServletContext、ServletConfig、ApplicationContext、DBPool多线程
单例模式的常见写法:并发
1.饿汉式单例: 在单例类首次加载时就建立实例。ide
//优势: 1 没有加任何的锁、执行效率比较高,在用户体验上来讲,比懒汉式更好 2 绝对线程安全,在线程还没出现之前就是实例化了,不可能存在访问安全问题 //缺点:类加载的时候就初始化,无论用仍是不用,都占着空间,浪费了内存 public class HungrySingleton { // 加final的缘由,若是不加能够经过反射等方法进行覆盖, 就不是单例对象了 private static final HungrySingleton hungrySingleton = new HungrySingleton(); private HungrySingleton(){} public static HungrySingleton getInstance(){ return hungrySingleton; } }
//饿汉式静态块单例 public class HungryStaticSingleton { private static final HungryStaticSingleton hungrySingleton; static { hungrySingleton = new HungryStaticSingleton(); } private HungryStaticSingleton(){} public static HungryStaticSingleton getInstance(){ return hungrySingleton; } }
2. 懒汉式单例: 被外部类调用时才建立实例性能
public class LazySimpleSingleton { private LazySimpleSingleton(){} //静态块,公共内存区域 private static LazySimpleSingleton lazy = null; public static LazySimpleSingleton getInstance(){ // 多线程状况下会出现线程安全问题 if(lazy == null){ lazy = new LazySimpleSingleton(); } return lazy; } }
public class LazySimpleSingleton { private LazySimpleSingleton(){} //静态块,公共内存区域 private static LazySimpleSingleton lazy = null; // JDK 1.6以后对synchronized性能优化了很多, 不可避免地仍是存在必定的性能问题 // 在方法上而且用static修饰, 可能会形成死锁 public synchronized static LazySimpleSingleton getInstance(){ if(lazy == null){ lazy = new LazySimpleSingleton(); } return lazy; } }
public class ExectorThread implements Runnable{ @Override public void run() { LazySimpleSingleton singleton = LazySimpleSingleton.getInstance(); System.out.println(Thread.currentThread().getName() + ":" + singleton); } }
public class LazySimpleSingletonTest { public static void main(String[] args) { Thread t1 = new Thread(new ExectorThread()); Thread t2 = new Thread(new ExectorThread()); t1.start(); t2.start(); System.out.println("End"); } }
public class LazyDoubleCheckSingleton { private volatile static LazyDoubleCheckSingleton lazy = null; private LazyDoubleCheckSingleton(){} public static LazyDoubleCheckSingleton getInstance(){ if(lazy == null){ synchronized (LazyDoubleCheckSingleton.class){ if(lazy == null){ lazy = new LazyDoubleCheckSingleton(); // cpu执行时会转化成JVM指令执行 // 指令重排序(线程执行抢时间, 可能先执行2再执行3, 也可能先执行3再执行2),在多线程或线程所得状况下可能会发生 // 解决方法加 volatile 关键字 //1.分配内存给这个对象 //2.初始化对象 //3.设置lazy指向刚分配的内存地址 //4.初次访问对象 } } } return lazy; } }
//懒汉式单例 //这种形式兼顾饿汉式的内存浪费,也兼顾synchronized性能问题 //完美地屏蔽了这两个缺点 public class LazyInnerClassSingleton { //默认使用LazyInnerClassGeneral的时候,会先初始化内部类 //若是没使用的话,内部类是不加载的 private LazyInnerClassSingleton(){ if(LazyHolder.LAZY != null){ throw new RuntimeException("不容许建立多个实例"); } } //每个关键字都不是多余的 //static 是为了使单例的空间共享 //保证这个方法不会被重写,重载 public static final LazyInnerClassSingleton getInstance(){ //在返回结果之前,必定会先加载内部类 return LazyHolder.LAZY; } //默认不加载 private static class LazyHolder{ private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton(); } }
3 反射破坏单例优化
public static void main(String[] args) { try{ //很无聊的状况下,进行破坏 Class<?> clazz = LazyInnerClassSingleton.class; //经过反射拿到私有的构造方法 Constructor c = clazz.getDeclaredConstructor(null); //强制访问 c.setAccessible(true); //暴力初始化 Object o1 = c.newInstance(); //调用了两次构造方法,至关于new了两次 //犯了原则性问题, Object o2 = c.newInstance(); System.out.println(o1 == o2); }catch (Exception e){ e.printStackTrace(); } }
4 序列化破坏单例ui
//反序列化时致使单例破坏 public class SeriableSingleton implements Serializable { //序列化就是说把内存中的状态经过转换成字节码的形式 //从而转换一个IO流,写入到其余地方(能够是磁盘、网络IO) //反序列化 //将已经持久化的字节码内容,转换为IO流 //经过IO流的读取,进而将读取的内容转换为Java对象 //在转换过程当中会从新建立对象new public final static SeriableSingleton INSTANCE = new SeriableSingleton(); private SeriableSingleton(){} public static SeriableSingleton getInstance(){ return INSTANCE; } }
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(); } } }
System.out.println(s1 == s2) 打印结果为false
解决办法:
public class SeriableSingleton implements Serializable { //序列化就是说把内存中的状态经过转换成字节码的形式 //从而转换一个IO流,写入到其余地方(能够是磁盘、网络IO) //内存中状态给永久保存下来了 //反序列化 //将已经持久化的字节码内容,转换为IO流 //经过IO流的读取,进而将读取的内容转换为Java对象 //在转换过程当中会从新建立对象new public final static SeriableSingleton INSTANCE = new SeriableSingleton(); private SeriableSingleton(){} public static SeriableSingleton getInstance(){ return INSTANCE; } // 重写 readResolve()方法, 只不过是覆盖了反序列化出来的对象 // 仍是建立了两次, 发生在JVM 层面, 相对来讲比较安全 // 以前反序列化出来的对象仍是会被JVM回收 private Object readResolve(){ return INSTANCE; } }
这时System.out.println(s1 == s2);打印结果为true
ObjectInputStream..readObject()源码中会去判断这个类是否有readResolve()这个方法
5 注册式单例: 将每个实例都缓存到统一的容器中,使用惟一标识获取实例
分为两种: 枚举式、容器式
枚举式:
public enum EnumSingleton { INSTANCE; private Object data; public Object getData() { return data; } public void setData(Object data) { this.data = data; } public static EnumSingleton getInstance(){ return INSTANCE; } }
/* public static void main(String[] args) { try { EnumSingleton instance1 = null; EnumSingleton instance2 = EnumSingleton.getInstance(); instance2.setData(new Object()); FileOutputStream fos = new FileOutputStream("EnumSingleton.obj"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(instance2); oos.flush(); oos.close(); FileInputStream fis = new FileInputStream("EnumSingleton.obj"); ObjectInputStream ois = new ObjectInputStream(fis); instance1 = (EnumSingleton) ois.readObject(); ois.close(); System.out.println(instance1.getData()); System.out.println(instance2.getData()); System.out.println(instance1.getData() == instance2.getData()); // true }catch (Exception e){ e.printStackTrace(); } }*/ public static void main(String[] args) { try { Class clazz = EnumSingleton.class; Constructor c = clazz.getDeclaredConstructor(String.class,int.class); c.setAccessible(true); EnumSingleton enumSingleton = (EnumSingleton)c.newInstance("BBA",112); // 从JDK层面就为枚举不被反序列化和反射作了限制 }catch (Exception e){ // IllegalArgumentException e.printStackTrace(); } }
容器式:
//Spring中的作法,就是用这种注册式单例 public class ContainerSingleton { private ContainerSingleton(){} private static Map<String,Object> ioc = new ConcurrentHashMap<String,Object>(); public static Object getInstance(String className){ synchronized (ioc) { if (!ioc.containsKey(className)) { Object obj = null; try { obj = Class.forName(className).newInstance(); ioc.put(className, obj); } catch (Exception e) { e.printStackTrace(); } return obj; } else { return ioc.get(className); } } } }
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(); } }
public class Pojo { }
public static void main(String[] args) { try { long start = System.currentTimeMillis(); ConcurrentExecutor.execute(new ConcurrentExecutor.RunHandler() { public void handler() { Object obj = ContainerSingleton.getInstance("com.pattern.singleton.Pojo"); System.out.println(System.currentTimeMillis() + ": " + obj); } }, 10,6); long end = System.currentTimeMillis(); System.out.println("总耗时:" + (end - start) + " ms."); }catch (Exception e){ e.printStackTrace(); } }
6 ThreadLocal线程单例: 保证线程内部的全局惟一,且天生线程安全
public class ThreadLocalSingleton { private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance = new ThreadLocal<ThreadLocalSingleton>(){ @Override protected ThreadLocalSingleton initialValue() { return new ThreadLocalSingleton(); } }; private ThreadLocalSingleton(){} public static ThreadLocalSingleton getInstance(){ return threadLocalInstance.get(); } }
public class ExectorThread implements Runnable{ @Override public void run() { ThreadLocalSingleton singleton = ThreadLocalSingleton.getInstance(); System.out.println(Thread.currentThread().getName() + ":" + singleton); } }
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()); Thread t1 = new Thread(new ExectorThread()); Thread t2 = new Thread(new ExectorThread()); t1.start(); t2.start(); System.out.println("End"); } }
ThreadLocal 也是一个注册式单例 ThreadLocal 伪线程安全 使用场景: 使用ThreadLocal来实现多数据源动态切换
单例模式的优势: 1 在内存中只有一个实例,减小了内存开销
2 能够避免对资源的多重占用
3 设置全局访问点,严格控制访问
缺点: 1 没有接口,扩展困难
2 要扩展单例对象,只有修改代码,没有其余途径