单例
单例的方式
-
饿汉式
在实例使用之前,不管你用不用,先new,避免线程安全
-
懒汉式
默认加载的时候不实例化,在需要的时候才实例化,延迟加载
-
注册登记式
每使用一次,都向一个固定的容器中注册并且将使用过的对象进行缓存,下次取对象的时候,就直接从缓存中取值,保证每次获取都是同一个对象,IOC的单例,就是注册登记单例
-
枚举式
只要设置INSTANCE枚举构造函数
-
序列化与反序列化
重新写readResolve()
饿汉式
public class Hungry { private Hungry(){ } //先静态,后动态 //先属性,后方法 //先上后下 private static Hungry hungry =new Hungry(); public static Hungry getHungry() { System.out.println(System.currentTimeMillis()+","+hungry); return hungry; } }
测试代码:
public static void main(String[] args) throws InterruptedException { int count=10; CountDownLatch countDownLatch=new CountDownLatch(count); for (int i=0;i<count; i++){ Thread.sleep(1000); new Thread(()-> { try { System.out.println(System.currentTimeMillis()+","+ Hungry.getHungry()); // System.out.println(countDownLatch.getCount()); countDownLatch.countDown(); } catch (Exception e) { e.printStackTrace(); } }).start(); } countDownLatch.await(); System.out.println("执行完成"); // System.out.println( Hungry.getHungry()); }
结果:
1533773048357,com.zjm.single.Hungry@63d8e973 1533773049366,com.zjm.single.Hungry@63d8e973 1533773050370,com.zjm.single.Hungry@63d8e973 1533773051375,com.zjm.single.Hungry@63d8e973 1533773052394,com.zjm.single.Hungry@63d8e973 1533773053401,com.zjm.single.Hungry@63d8e973 1533773054415,com.zjm.single.Hungry@63d8e973 1533773055417,com.zjm.single.Hungry@63d8e973 1533773056418,com.zjm.single.Hungry@63d8e973 1533773057436,com.zjm.single.Hungry@63d8e973
从上面结果看出,饿汉式操作单例是线程安全的
懒汉式
代码:
package com.zjm.single; public class LazyOne { private LazyOne(){} private static LazyOne lazyOne=null; public static LazyOne getInstance() { if(lazyOne==null){ lazyOne=new LazyOne(); } return lazyOne; } }
上个测试代码改成LayOne.getInstance(),CountDownLach的个数成多一点例如:100
测试结果:
1533773305018,com.zjm.single.LazyOne@6f2b6701 1533773305018,com.zjm.single.LazyOne@315cb0c2 1533773305018,com.zjm.single.LazyOne@315cb0c2 1533773305018,com.zjm.single.LazyOne@315cb0c2 1533773305018,com.zjm.single.LazyOne@315cb0c2 1533773305018,com.zjm.single.LazyOne@74b47802 1533773305018,com.zjm.single.LazyOne@315cb0c2 1533773305018,com.zjm.single.LazyOne@315cb0c2 1533773305018,com.zjm.single.LazyOne@74b47802 1533773305018,com.zjm.single.LazyOne@5eca8d3a 1533773305018,com.zjm.single.LazyOne@315cb0c2 1533773305018,com.zjm.single.LazyOne@315cb0c2 1533773305018,com.zjm.single.LazyOne@315cb0c2 1533773305018,com.zjm.single.LazyOne@315cb0c2 1533773305018,com.zjm.single.LazyOne@315cb0c2 1533773305018,com.zjm.single.LazyOne@315cb0c2 1533773305018,com.zjm.single.LazyOne@315cb0c2 1533773305018,com.zjm.single.LazyOne@315cb0c2 1533773305018,com.zjm.single.LazyOne@315cb0c2 1533773305018,com.zjm.single.LazyOne@315cb0c2 1533773305018,com.zjm.single.LazyOne@315cb0c2 1533773305027,com.zjm.single.LazyOne@315cb0c2
看结果当前的实例不是同一个实例,懒汉式是线程不安全,要想是线程安全,可以加锁
public syncronized static LazyOne getInstance()
还有一种方式:
public class LazyThree { private static boolean initalized=false; //默认使用lazyThree的时候,先回初始化内部类 private LazyThree(){ synchronized (LazyThree.class){ if (initalized==false){ initalized=!initalized; }else{ throw new RuntimeException("单例已经初始化"); } } } private static LazyThree lazyThree=null; private final static LazyThree getInstance() { return lazyHolder.LAZY_THREE; } private static class lazyHolder{ public static final LazyThree LAZY_THREE=new LazyThree(); } }
上面这种方式首先,我现在加载内部类,这个内部类的实例是不会改变的,当我用到的时候再去初始化。
测试代码:
public class LazyThreeTest { public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException { Class<?> clazz= LazyThree.class; Constructor cu=clazz.getDeclaredConstructor(null); cu.setAccessible(true); Object o= cu.newInstance(); Object o1=cu.newInstance(); //System.out.println(o==o1); } }
利用反射机制去查看实例是否能被多次调用
结果:
Exception in thread "main" java.lang.reflect.InvocationTargetException at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at com.zjm.single.test.LazyThreeTest.main(LazyThreeTest.java:14) Caused by: java.lang.RuntimeException: 单例已经初始化 at com.zjm.single.LazyThree.<init>(LazyThree.java:12) ... 5 more
上述结果不能实例化两次调用
注册登记式(spring Ioc容器会用到)
/** * spring 方式去注册单例 */ public class BeanFactory { private BeanFactory(){} private static Map<String,Object> ioc=new ConcurrentHashMap<>() ; public static Object getBean(String className) throws ClassNotFoundException, IllegalAccessException, InstantiationException { if(!ioc.containsKey(className)){ Object obj=null; obj=Class.forName(className).newInstance(); return obj; }else{ return ioc.get(className); } } }
测试代码:
public class BeanTest { public static void main(String[] args) { int count=100; CountDownLatch countDownLatch=new CountDownLatch(count); for (int i=0;i<count; i++){ // Thread.sleep(2000); new Thread(()-> { try { countDownLatch.await(); // System.out.println(countDownLatch.getCount()); } catch (Exception e) { e.printStackTrace(); } try { Object o=BeanFactory.getBean("com.zjm.single.Vo"); System.out.println(o); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } }).start(); countDownLatch.countDown(); } // System.out.println("执行完成"); } }
测试结果(部分数据):
com.zjm.single.Vo@749754de com.zjm.single.Vo@623cb696 com.zjm.single.Vo@4d0d064b com.zjm.single.Vo@55950ec com.zjm.single.Vo@1b3f359d com.zjm.single.Vo@7a978f7d com.zjm.single.Vo@7a978f7d com.zjm.single.Vo@7a978f7d com.zjm.single.Vo@7a978f7d com.zjm.single.Vo@7a978f7d com.zjm.single.Vo@7a978f7d com.zjm.single.Vo@623cb696 com.zjm.single.Vo@2ceac56c com.zjm.single.Vo@7a978f7d com.zjm.single.Vo@6ce865f6 com.zjm.single.Vo@39bfe578 com.zjm.single.Vo@63fb5fe6
线程不安全必须要加上 public syncronized static Object getBean()
序列化和反序列化
public class Seriable implements Serializable { //序列化就是说把内存中的状态通过转换成字节码的形式 //从而转换一个IO流,写入到其他地方(可以是磁盘、网络IO) //内存中状态给永久保存下来了 //反序列化 //讲已经持久化的字节码内容,转换为IO流 //通过IO流的读取,进而将读取的内容转换为Java对象 //在转换过程中会重新创建对象new public final static Seriable INSTANCE =new Seriable(); private Seriable(){ } public static Seriable getInstance(){ return INSTANCE; } //这个是自带的方法序列化JVM虚拟机 private Object readResolve(){ return INSTANCE; } }
为了保证数据保存的对象的时候不会再new
测试代码:
public class SeriableTest { public static void main(String[] args) { Seriable s1=null; Seriable s2=Seriable.getInstance(); FileOutputStream fos=null; try{ fos=new FileOutputStream("Seriable.obj");//保存序列化的数据,输出到磁盘中或者网络IO ObjectOutputStream oos=new ObjectOutputStream(fos);//将io流写入 oos.writeObject(s2);//将输出流s2写入 oos.flush();//刷新 oos.close();//关闭 FileInputStream fis=new FileInputStream("Seriable.obj"); ObjectInputStream ois=new ObjectInputStream(fis); s1= (Seriable) ois.readObject(); System.out.println(s1+"\n"+s2); System.out.println(s1==s2); }catch (Exception e){ } }
输入对象和输出对象是否一致
测试结果:
com.zjm.single.seriable.Seriable@135fbaa4 com.zjm.single.seriable.Seriable@135fbaa4 true
上述是一个实例所以证明是单例
private Object readResolve(){ return INSTANCE; }
这个代码块去掉
结果为false