彻底玩转单例模式
单例模式无论 new多少次,返回的实例对象在内存中的地址是一样的
1、饿汉式 在类加载的时候完成 类的初始化。
/** * * 一、饿汉式的意思 * 类被加载完成后,对象已经被创建了。 * * 二、饿汉式的缺点 * 饿汉式可能会造成内存的浪费,如下面的例子,EHan 被加载后,data 也会被初始化。造成data 浪费内存。 * * */ public class EHan { //饿汉式单例 被jvm 加载了后 这个属性还没用呢,可能造成内存的浪费。 private byte[] data = new byte[1024*1024]; private EHan(){ System.out.println("正在创建类"); } private static final EHan HUNGRY = new EHan(); public static EHan getHungry(){ return HUNGRY; } //比如说这个方法被调用的时候EHan 已经被加载了,同时这类也创建了一个实例private static final EHan HUNGRY = new EHan(); public static void ts(){ System.out.println("ceshi"); } }
2、懒汉式
/** * 一、懒汉式的意思 * 类被加载完成后,对象没有被创建呢,用的时候才创建对象 * 二、懒汉式的缺点 * 懒汉式,单线程下是没有问题的,多线程是有问题的。 * 多个线程下来获取对象的时候不同的线程获取多个对象。内存里面会有多个对象存在。 * */ public class LazyHan { private LazyHan(){ System.out.println(Thread.currentThread().getName()+"正在创建对象"); } private static LazyHan lazyHan; public static LazyHan getLazyHan(){ if(lazyHan == null){ lazyHan = new LazyHan(); } return lazyHan; }; //比如说这个方法被调用的时候EHan 已经被加载,但是 这个类的对象还是null(private static LazyHan lazyHan;). 构造方法也没有执行 // 只有getLazyHan 这个方法被调用的时候才创建对象。 public static void test1(){ System.out.println("ceshi zhong "); } public static void main(String[] args) { int num = Runtime.getRuntime().availableProcessors(); ThreadPoolExecutor excutors = new ThreadPoolExecutor( 1, num, 1000, //超时等待的时间,就是345 中的窗口 这段时间没有人办理业务就要关闭,释放线程了。 TimeUnit.SECONDS, new LinkedBlockingQueue(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());//其中的一个拒绝策略, try{ for (int i = 0; i < 94; i++) { excutors.execute(()->{ LazyHan.getLazyHan(); }); } }catch (Exception e){ e.printStackTrace(); }finally { excutors.shutdown(); } } }
3、双重检查锁模式
import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * * 一、双重检查锁模式的意思是什么? * 两次判断对象是否为null加锁 这就是双重检查锁模式的意思 * * 二、具体的实现 * 判断 对象是不是空 是空的化 进入一个同步方法 方法里面 再判断了下 对象 是空就 new 对象 是推荐使用的。 * 解决线程安全问题 和 实现懒加载。 * */ public class SCS { private SCS(){ System.out.println(Thread.currentThread().getName()+"正在创建对象"); } // volatile 保证内存可见行、不保证原子性、禁止指令重排。这里加volatile 的原因主要是禁止指令重排防止极端情况下,返回的对象有问题。 private volatile static SCS scs; public static final SCS getSCS(){ if(scs == null){ synchronized (SCS.class){ if(scs == null){ scs = new SCS(); //这里不是一个 原子性的操作 /** * 创建对象的内存操作 * 1、分配内存空间 * 2、执行构造方法 * 3、把这个对象指向这个空间 * * 我们期望执行的时候是123 步骤来的,但是如果发生了指令重排导致执行步骤变成132,此时来了一个线程发现对象已经指向 * 了空间认为这个对象已经创建好了,但是没有执行构造方法,返回一个指向虚无的对象产生一些问题 * 为了防止出现这样的方式必须给 对象加一个 volatile * * * */ } } } return scs; } public static void main(String[] args) { int num = Runtime.getRuntime().availableProcessors(); ThreadPoolExecutor excutors = new ThreadPoolExecutor( 1, num, 1000, //超时等待的时间,就是345 中的窗口 这段时间没有人办理业务就要关闭,释放线程了。 TimeUnit.SECONDS, new LinkedBlockingQueue(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());//其中的一个拒绝策略, try{ for (int i = 0; i < 94; i++) { excutors.execute(()->{ SCS.getSCS(); }); } }catch (Exception e){ e.printStackTrace(); }finally { excutors.shutdown(); } }
4、静态内部类的方式
/** * * 内部类的方式创建单例模式 * 这样的方式也不是安全的 * */ public class OutClass { private OutClass(){}; public static OutClass getOutClass(){ return InnerClass.outClass; }; public static class InnerClass{ private static OutClass outClass = new OutClass(); } }
5、枚举类的方式获取对象
/** * * 枚举的方式创建单例模式 * * 除了枚举类创建单例别的方式都可以通过反射的方式破坏单例模式。 * * */ public enum EnumSingle { INSTANCE; public static EnumSingle getInstance(){ return INSTANCE; } public void test1(){ System.out.println("测试通过枚举的方式创建类"); } }