【设计模式】单例模式
1.单例模式有以下特点:
(1)单例类只能有一个实例。
(2)单例类必须自己创建自己的唯一实例。
(3)单例类必须给所有其他对象提供这一实例。
单例模式的作用就是保证在整个应用程序的生命周期中,任何一个时刻,单例类的实例都只存在一个。
任何一个单例模式至少要满足以下的三大点:
(1)必须有一个私有的静态变量指向本类本身。
(2)必须有一个私有、参数为空的构造器。
(3)必须有一个公有、静态方法、返回自身实例。
2.懒汉式单例
package SingletonMode; public class Singleton { private static Singleton singleton; private Singleton() {} public static Singleton getInstance() { if(singleton==null) { singleton=new Singleton(); } return singleton; } }
package SingletonMode; public class Client1 { public static void main(String[] args) { Singleton c1 = Singleton.getInstance(); Singleton c2 = Singleton.getInstance(); System.out.println(c1); System.out.println(c1); System.out.println(c1==c2); } }
运行结果:
从以上结果可知,虽然创建了2个不同的实例,但是实际上访问的是同一个实例。由于构造函数被设置为 private ,所以无法再在 Singleton 类的外部使用 new 来实例化一个实例,只能通过访问 getInstance()来访问 Singleton 类。
在多线程环境下,我们就要考虑到线程的安全问题:即是否有不同的线程分别new一个对象实例,导致不同线程创建的class类的实例不是同一个。
第一种方法:在方法加上同步synchronized
package SingletonMode; public class Singleton { private static Singleton singleton; private Singleton() {} public static synchronized Singleton getInstance() { if(singleton==null) { singleton=new Singleton(); } return singleton; } }
但synchronize锁是要消耗资源的,所以直接用synchronize锁锁住getInstance方法的话,每个线程来的时候都会额外造成synchronize锁带来的资源消耗问题。
第二种方法:双重检测锁
package SingletonMode; public class Singleton { private static Singleton singleton; private Singleton() {} public static Singleton getInstance() { if(singleton==null) { synchronized(Singleton.class) { if(singleton==null) { singleton=new Singleton(); } } } return singleton; } }
不把synchronize锁加到getInstance方法上面,而是加到singleton==null这个if判断的代码块上面,然后再在里面加上singleton==null的判断。
第一个if判断,是为了节省synchronize锁带来的额外资源消耗,保证一旦singleton已经被实例化,后面的线程就不用再new 类的实例了。
而第二个if判断,则是为了解决线程安全问题,因为可能会有多个线程同时运行到singleton==null这行代码,这就会造成多个线程会继续执行下去,执行singleton=new Singleton();
这还是会产生多个不同的Singleton的singleton实例。但是多加一个singleton == null判断,即使有多个线程同时运行到synchronize锁这个部分,但是这个代码块只能有一个线程进入,
所以第一个线程进入后,singleton被实例化,其他线程之后进入后,也无法执行singleton=new Singleton();这行代码。(原文链接:https://blog.csdn.net/sinat_41712356/article/details/117128051)
第三种方法:静态内部类
package SingletonMode; public class Singleton { private static class LazyHolder{ private static final Singleton singleton=new Singleton(); } private Singleton() {} public static final Singleton getInstance() { return LazyHolder.singleton; } }
方法三比方法一、方法二更好,减少同步的性能影响。
静态内部类是在外部类调用时才加载的,也就是说静态内部类可以在外部类加载之后,再加载到jvm中。所以我们在静态内部类中设置一个静态变量,然后指定初始值。因为我们之前提到的类在加载过程中,静态变量只会被初始化一次,所以在jvm层面中,保证了线程的安全性。因为静态内部类被调用之后才会加载初始化,所以就避免了资源浪费问题。(原文链接:https://blog.csdn.net/sinat_41712356/article/details/117128051)
3.饿汉式单例
饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。
package SingletonMode; public class Singleton1 { private static Singleton1 singleton = new Singleton1(); private Singleton1() {} public static Singleton1 getInstance() { return singleton; } }
4.饿汉式和懒汉式区别
创建形式 | 线程安全 | |
懒汉式单例 | 使用时才创建实例 | 否(使用同步、双重检查锁定、静态内部类实现线程安全) |
饿汉式单例 | 事先创建实例 | 是 |
参考文章:
https://www.cnblogs.com/BoyXiao/archive/2010/05/07/1729376.html
https://blog.csdn.net/jason0539/article/details/23297037
https://blog.csdn.net/sinat_41712356/article/details/117128051