单例模式提供了创建一个类唯一对象的方式。
单例模式情况下,某一个类只有唯一实例,且该实例可以被其他所有对象引用。
单例模式的关键点:
1.构造器私有化
2.实例对象静态化
构造器私有化后,无法通过new来创建,只能通过该类提供的方法获取实例对象。
对象静态化后可保证全局有效,使获取的对象始终是一个对象。
单例模式的实现方法:
1、懒汉式(线程不安全、延时加载)
延时加载即在调用时才会加载。
//懒汉式,非同步 线程不安全//由于其非同步,所以严格意义上来说不属于单例模式//在多线程下无法正常工作。class Singleton{ private static Singleton singleton; private Singleton() {} public static Singleton getInstance() { if(singleton == null) { singleton = new Singleton(); } return singleton; } public void printObject() { System.out.println(this); }}
这种单例模式不适用于多线程情况。
2、懒汉式(线程安全、延时加载)
//线程安全,可以在多线程中使用,但效率低下。////class Singleton{ // 懒汉式线程安全 private static Singleton singleton; private Singleton() {} public synchronized static Singleton getInstance() { if(singleton == null) { singleton = new Singleton(); } return singleton; } public void printObject() { System.out.println(this); }}
这里通过synchronized关键字对其加锁,保证了在多线程情况下的安全,但是锁住整个方法效率较低。
3、饿汉式(线程安全,非延时加载)
即在初始化就完成加载,而不是在调用时完成加载。
//饿汉式//类在加载的时候就实例化了,就算thread1和thread2同时获取它,//取到的是类加载时实例化的那个变量的值,所以说是线程安全的。//饿汉式缺点是类加载是就初始化对象,浪费内存。class Singleton{ private static Singleton singleton = new Singleton(); private Singleton() {} public static Singleton getInstance() { return singleton; } public void printObject() { System.out.println(this); } }
饿汉式在类加载时就创建了对象,避免了多线程访问的并发问题。
但其类加载时就需要实例化对象,消耗内存。
4、双重校验锁(线程安全,延时加载)
//双重校验锁//多线程下能保持高性能class Singleton{ private volatile static Singleton singleton; private Singleton() {} public static Singleton getInstance() { if(singleton == null) { synchronized(Singleton.class) { //此时锁住的是类 if(singleton == null) singleton = new Singleton(); } } return singleton; } public void printObject() { System.out.println(this); }}
采用双检锁既保证了多线程下的安全性,又保持了较好的性能。
因为当对象存在时,第一判空条件上没有添加synchronized,不会降低效率。
后续创建对象实例部分才添加了synchronized关键字锁住了类,然后再判断对象是否为空,保证了安全性。
5、登记式/静态内部类(线程安全、延时加载)
class Singleton{ private Singleton() {}; //静态内部类 private static class SingletonHolder{ private static final Singleton singleton = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.singleton; } public void printObject() { System.out.println(this); }}
静态内部类加载后并不会立刻加载内部的静态属性,只有外部调用内部类才会初始化静态属性,加载实例。
这样既保证了多线程情况下的安全性,而且实现了延迟加载。
参考资料: