单列模式
单例模式就是为了确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。单例模式控制控制对象不要反复创建,提高我们工作的效率。减少资源的占用。
【解释:我有 6 个漂亮的老婆,她们的老公都是我,我就是我们家里的老公 Sigleton,她们只要说道「老公」,都是指的同一个人,那就是我 。】
(1)单例模式下类的组成部分:
1.私有的构造方法。
2.私有的当前类对象作为静态属性。
3.公有的向外界提供当前类对象的静态方法。
主要的两种实现方式:
饿汉式:线程安全,调用效率高,不能延时加载。
懒汉式:线程安全,调用效率不高,可以延时加载。
其他方式:
双重检测锁式:极端情况下偶尔会出现问题,不建议使用。
静态内部类式:线程安全,调用效率高,可以延时加载。
枚举式:线程安全,调用效率高,不能延时加载。
(2)懒汉式和饿汉式的区别
“懒汉式”是在你真正用到的时候才去建这个单例对象,“饿汉式”是在不管用不用得上,一开始就建立这个单例对象。
从实现方式来讲他们最大的区别就是懒汉式是延时加载,他是在需要的时候才创建对象,而饿汉式在虚拟机启动的时候就会创建,饿汉式无需关注多线程问题,写法简单明了,能用则用。但是它是加载类时创建实例。
饿汉式是线程安全的,在类创建的同时就已经创建好一个静态的对象供系统使用,以后不在改变。懒汉式如果在创建实例对象时不加上synchronized则会导致对对象的访问不是线程安全的。
只是一个语法的标识,就好比文档中的句号和逗号差不多,代表当前是一个完整的语句,和下一个语法分开,如果仅仅只有一句,加不加无所谓,但是多个语句时,要区分,否则编译器不知道你的语法。
1、饿汉式
类加载到内存后,就实例化一个单例,JVM保证线程安全。
简单实用,推荐使用!
唯一缺点:不管用到与否,类装载时就完成实例化。
public class Singleton01 { //持有私有静态实例,防止被引用 private static final Singleton01 INSTANCE = new Singleton01(); // private static final Singleton01 INSTANCE; // static { // INSTANCE = new Singleton01(); // } //私有构造方法,防止被实例化 //只有本类能调用,只能Singleton01 Singleton = Singleton01.getInstance();调用 private Singleton01() {};//分号:增加语法的可读性 /*只是一个语法的标识,就好比文档中的句号和逗号差不多,代表当前是一个完整的语句,和下一个语法分开,如果仅仅只有一句,加不加无所谓,但是多个语句时,要区分,否则编译器不知道你的语法。*/ //懒汉式,静态工程方法,创建实例 public static Singleton01 getInstance() { return INSTANCE; } //可有可无 public void m() { System.out.println("m"); } public static void main(String[] args) { Singleton01 m1 = Singleton01.getInstance(); Singleton01 m2 = Singleton01.getInstance(); System.out.println(m1 == m2); } }
2、懒汉式(lazy loading)
虽然达到了按需初始化的目的,但却带来线程不安全的问题。
可以通过synchronized解决,但也带来效率下降。延迟加载,也叫作懒加载,等到真正用的时候才加载。
public class Singleton02 { //持有私有静态实例,防止被引用 private static Singleton04 INSTANCE; //私有构造方法,防止被实例化 private Singleton02() { } //懒汉式,静态工程方法,创建实例 //加锁(synchronized)效率下降 public static /*synchronized*/ Singleton02 getInstance() { if (INSTANCE == null) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } //妄图通过减小同步代码块的方式提高效率,然后不可行 // synchronized (Singleton02.class) { // try { // Thread.sleep(1); // } catch (InterruptedException e) { // e.printStackTrace(); // } INSTANCE = new Singleton02(); } return INSTANCE; } public void m() { System.out.println("m"); } public static void main(String[] args) { for(int i=0; i<100; i++) { new Thread(()->{ System.out.println(Singleton02.getInstance().hashCode()); }).start(); } } }
3、双重检测
保证线程安全;以前认为是最完美的方式。
public class Singleton03 { //volatile:不写,会出现。volatile 防止指令重排和可见性 private static volatile Singleton03 INSTANCE; //JIT private Singleton03() { } public static Singleton03 getInstance() { //判断对象是否已经实例化,只有非实例化的情况下才能进入if块进行加锁。 if (INSTANCE == null) { //双重检查:保证线程安全;以前认为是最完美的方式 synchronized (Singleton03.class) { //避免 SingletonTon== null时,第一个线程实例化后,进入阻塞状态的线程被唤醒后仍会进行实例化。 if(INSTANCE == null) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } INSTANCE = new Singleton03(); } } } return INSTANCE; } public void m() { System.out.println("m"); } public static void main(String[] args) { for(int i=0; i<100; i++) { new Thread(()->{ System.out.println(Singleton03.getInstance().hashCode()); }).start(); } } }
4、静态内部类(完美)
静态内部类方式,完美。JVM保证单例线程安全,加载外部类时不会加载内部类,这样可以实现懒加载。
public class Singleton04 {//JVM只加载一次 //私有构造方法 private Singleton04() { } //静态内部类,初始化04 private static class Singleton07Holder { //组合外部类对象作为属性 private final static Singleton04 INSTANCE = new Singleton04();//出现延迟加载 } public static Singleton04 getInstance() { return Singleton04Holder.INSTANCE; } public void m() { System.out.println("m"); } public static void main(String[] args) { for(int i=0; i<100; i++) { new Thread(()->{ System.out.println(Singleton04.getInstance().hashCode()); }).start(); } } }
5、枚举单列(最完美)
不仅可以解决线程同步,还可以防止反序列化。由JVM从根本上提供保障!避免通过反射和反序列化的漏洞!
public enum Singletontonton05 { INSTANCE; public void m() {} public static void main(String[] args) { for(int i=0; i<100; i++) { new Thread(()->{ System.out.println(Singletonton05.INSTANCE.hashCode()); }).start(); } } }