饿汉式单例模式,实例对象在初始化的时候创建,不管有没有得到调用。好处是没有线程安全的问题,坏处是浪费内存空间。
//饿汉式单例 public class ehan { /** * 存在的问题: * 饿汉式强调的是一上来就初始化对象,初始化的对象里面又有很多像如下定义的很多数组同时也被初始化, * 但没有得到调用,就会存在浪费空间的情况 * */ private byte[] data1 = new byte[1024*1024]; private byte[] data2 = new byte[1024*1024]; private byte[] data3 = new byte[1024*1024]; private byte[] data4 = new byte[1024*1024]; //构造器私有,保证别的方法无法调用生成这个对象 private ehan(){} //实例对象初始化时创建 private static final ehan HUNGRY = new ehan(); public static ehan getInstance(){ return HUNGRY; } public static void main(String[] args) { ehan ehan1 = ehan.getInstance(); ehan ehan2 = ehan.getInstance(); System.out.println(ehan1==ehan2);//true } }我们知道饿汉式在初始化的时候就完成了对象的创建,但存在浪费空间的问题,因此,懒汉式单例模式应运而生。 懒汉式顾名思义就是只有当需要的时候才会去初始化数据。
//懒汉式单例模式 public class lanhan { //构造器私有 private lanhan(){ } private static lanhan lazyMan; public static lanhan getInstance(){ if(lazyMan==null){ lazyMan = new lanhan(); } return lazyMan; } public static void main(String[] args) { lanhan instance1 = lanhan.getInstance(); lanhan instance2 = lanhan.getInstance(); System.out.println(instance1==instance2); } }由于上述的懒汉式在单线程里确实是安全的,但是在多线程中,其实是并不安全的,我们可以通过以下代码测试: 我们会发现,得出的结果并不是单例的。
//懒汉式单例模式 public class lanhan { //构造器私有 private lanhan(){ System.out.println(Thread.currentThread().getName()+"OK"); } private static lanhan lazyMan; public static lanhan getInstance(){ if(lazyMan==null){ lazyMan = new lanhan(); } return lazyMan; } /** * 单线程下确实单例安全,但是多线程下是存在安全问题的 * 我们得加锁 * */ public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 10; i++) { new Thread(()->{ lanhan.getInstance(); }).start(); } TimeUnit.SECONDS.sleep(3); lanhan instance1 = lanhan.getInstance(); lanhan instance2 = lanhan.getInstance(); System.out.println(instance1==instance2); } }因此我们得给他上锁,保证一个线程只能创建一个对象,也有且只能有一个对象。同时为了防止指令重排现象出现,我们得对变量加volatile锁。
//DCL懒汉式单例模式 public class lanhan { //构造器私有 private lanhan(){ System.out.println(Thread.currentThread().getName()+"OK"); } //防止指令重排 private static volatile lanhan lazyMan; //双重检测锁模式--DCL懒汉式检测 public static lanhan getInstance(){ //先判断有没有对象实例被创建,没有的话就锁住这个类,确保只有一个Class对象 if(lazyMan==null){ synchronized (lanhan.class){ if(lazyMan==null){ //但是还可能存在一个问题,因为new lanhan()不是一个原子操作 /* * 1.分配内存 * 2.执行构造方法 * 3.指向地址 * 有可能存在的问题就是由于指令重排,它的执行顺序完全有可能是132,这个时候由于指向了内存地址, * 会被以为创建了对象,导致无法创建对象,为了保证不发生指令重排现象,我们必须加上volatile关键字 * */ lazyMan = new lanhan(); } } } return lazyMan; } public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 10; i++) { new Thread(()->{ lanhan.getInstance(); }).start(); } TimeUnit.SECONDS.sleep(3); lanhan instance1 = lanhan.getInstance(); lanhan instance2 = lanhan.getInstance(); System.out.println(instance1==instance2); } }可以延时加载,并且线程安全
//静态内部类实现 public class Holder { //构造器私有 private Holder(){ } public static Holder getInstance(){ return InnerClass.HOLDER; } //静态内部类 public static class InnerClass{ private static final Holder HOLDER = new Holder(); } public static void main(String[] args) { Holder instance = Holder.getInstance(); Holder instance2 = Holder.getInstance(); System.out.println(instance==instance2); } }java中存在反射机制,能够改变内部的private关键字,因此使用private修饰的其实都不算是安全的,由此引出枚举单例模式。
//反射破坏单例模式 public class fanshe { //构造器私有 private fanshe(){ System.out.println(Thread.currentThread().getName()+"OK"); } private static volatile fanshe lazyMan; public static fanshe getInstance(){ if(lazyMan==null){ synchronized (fanshe.class){ if(lazyMan==null){ lazyMan = new fanshe(); } } } return lazyMan; } public static void main(String[] args) throws Exception { fanshe instance = fanshe.getInstance(); //利用反射获取其对应的构造器方法 Constructor<fanshe> declaredConstructor = fanshe.class.getDeclaredConstructor(null); //无视私有构造器,通过反射创建对象 declaredConstructor.setAccessible(true); fanshe instance2 = declaredConstructor.newInstance(); System.out.println(instance==instance2); } }如何解决反射破坏单例现象?第一种方法,我们可以在构造器里面对类加锁。
//反射破坏单例模式 public class fanshe { //构造器私有 private fanshe(){ synchronized (fanshe.class){ if(lazyMan==null){ throw new RuntimeException("不要试图使用反射破坏异常"); } } } private static volatile fanshe lazyMan; public static fanshe getInstance(){ if(lazyMan==null){ synchronized (fanshe.class){ if(lazyMan==null){ lazyMan = new fanshe(); } } } return lazyMan; } public static void main(String[] args) throws Exception { fanshe instance = fanshe.getInstance(); //利用反射获取其对应的构造器方法 Constructor<fanshe> declaredConstructor = fanshe.class.getDeclaredConstructor(null); //无视私有构造器,通过反射创建对象 declaredConstructor.setAccessible(true); fanshe instance2 = declaredConstructor.newInstance(); System.out.println(instance==instance2); } }但是上面那种方法治标不治本,因为第一个instance我们是通过getInstance方法创建的,没经过反射,如果我们两个对象都是通过反射创建,如下代码所示,还是会存在问题。
//反射破坏单例模式 public class fanshe { //构造器私有 private fanshe(){ synchronized (fanshe.class){ if(lazyMan!=null){ throw new RuntimeException("不要试图使用反射破坏异常"); } } } private static volatile fanshe lazyMan; public static fanshe getInstance(){ if(lazyMan==null){ synchronized (fanshe.class){ if(lazyMan==null){ lazyMan = new fanshe(); } } } return lazyMan; } public static void main(String[] args) throws Exception { //fanshe instance = fanshe.getInstance(); //利用反射获取其对应的构造器方法 Constructor<fanshe> declaredConstructor = fanshe.class.getDeclaredConstructor(null); //无视私有构造器,通过反射创建对象 declaredConstructor.setAccessible(true); fanshe instance2 = declaredConstructor.newInstance(); fanshe instance3 = declaredConstructor.newInstance(); System.out.println(instance3==instance2); } }由此引入第二种方法,通过定义一个信号量来解决:
//反射破坏单例模式 public class fanshe { //定义一个信号量 private static boolean flag = false; //构造器私有 private fanshe(){ synchronized (fanshe.class){ //通过反射的方式无法获取到这个信号量,因此不能通过反射创建 if(flag==false){ flag=true; } else{ throw new RuntimeException("不要试图使用反射破坏异常"); } } } private static volatile fanshe lazyMan; public static fanshe getInstance(){ if(lazyMan==null){ synchronized (fanshe.class){ if(lazyMan==null){ lazyMan = new fanshe(); } } } return lazyMan; } public static void main(String[] args) throws Exception { //fanshe instance = fanshe.getInstance(); //利用反射获取其对应的构造器方法 Constructor<fanshe> declaredConstructor = fanshe.class.getDeclaredConstructor(null); //无视私有构造器,通过反射创建对象 declaredConstructor.setAccessible(true); fanshe instance2 = declaredConstructor.newInstance(); fanshe instance3 = declaredConstructor.newInstance(); System.out.println(instance3==instance2); } }利用枚举的方式创建单例模式,可以避免反射对其内部进行关键字的破坏,继而导致单例模式失效。它的缺点也是不能延时加载
public enum meiju{ INSTANCE; public meijugetInstance(){ return INSTANCE; } } class test05{ public static void main(String[] args) { meijuinstance1 = SingletonTest05.INSTANCE; meijuinstance2 = SingletonTest05.INSTANCE; System.out.println(instance1==instance2); } }