引言
你是我的唯一,我是你的单例。
大情至简、大爱至专
设计模式很多,最 浪漫 的莫过于单例。
因为单例的重要性和易用性,被大家广泛的应用于编码当中。
正因为其易用,反而被大家忽略了其重要性,产生了一些不必要的麻烦。
1、为什么需要单例
有的对象其实我们只需要一个,比如说:线程池、缓存、对话框、注册表、日志、驱动程序等。事实上,这类对象也只能有一个实例,如果制造了多个实例,就会导致许多问题产生。
2、初始化全局变量和单例模式的区别
如果将对象赋值给一个全局变量,那么必须在程序一开始就创建好对象,万一这个对象非常耗费资源,而程序在本次执行中却没有用 到,就形成了浪费。而单例模式可以在需要的时候才创建对象,这就是延迟实例化。
一、第一种单例模式:懒汉式
java在实例化对象的时候需要用到new关键字,前提是实例化的类要有公开的构造器,那么就可以通过new来创建多个对象。
如果被实例化的类没有公开的构造器,而是一个私有的构造器,那么如何来获取类对象呢?
这也是单例的巧妙之处,private的构造器是无法在本类之外调用的,所以只能在本类中实例化。
那么外部想要得到这个实例,就需要提供一个静态的方法将实例化好的对象返回出去。
懒汉式单例的要素:静态的本类全局变量、私有的构造器、getInstance方法
public class MyClass {
//静态的本类成员变量
private static MyClass myClass;
//私有的构造器
private MyClass() {
}
// 向外界提供的静态方法
public static MyClass getInstance() {
if (myClass == null) {
myClass = new MyClass();
}
return myClass;
}
}
这就是常见的懒汉式单例,因为使用的时候才开始创建(延迟实例化),所以称为“懒”汉式。
懒汉式虽然易用,但是其在线程安全方面仍有一些问题:
假设有两个线程Thread1和Thread2同时调用getInstance方法。
线程2在线程1已经创建出MyClass对象并且已经刷新内存之后调用不会出现问题。
但是线程1刚创建出MyClass对象时还没有刷新内存之前,线程2还是认为myClass对象为null。
那么就会创建出第二个myClass对象,所以线程是不安全的。
这种问题可以通过线程锁来解决:
//向外界提供的静态方法
public synchronized static MyClass getInstance() {
if (myClass == null) {
myClass = new MyClass();
}
return myClass;
}
使用线程锁可以规避多线程情况下产生不同的myClass对象,但是这种方法仍有弊端。
因为synchronized关键字会造成线程等待而降低程序的性能,同步一个方法可能造成程序运行的效率下降十倍百倍。
但是在不需要频繁操作getInstance方法时这种处理方式还是非常简洁实用的。
那么如何在线程安全的情况下又不降低程序的性能呢?这就是第二种单例模式。
二、第二种单例模式:饿汉式
之所以称为饿汉式,是因为其在本类初始化时就急切的创建类对象,在调用getInstance方法时直接返回创建好的对象。
public class MyClass {
// 静态的本类成员变量
private static MyClass myClass = new MyClass();
//私有的构造器
private MyClass() {
}
//向外界提供的静态方法
public static MyClass getInstance() {
return myClass;
}
}
这种方式在本类创建时就实例化本类对象,所以避开了延迟实例化,是一种线程安全的做法。
其缺点是依然静态初始化本类对象。
如何在使用延迟实例化的方式下使用线程安全的单例模式, 这就提到了第三种单例模式。
三、第三种单例模式:双重加锁
利用双重加锁,首先检查是否实例已经创建,如果尚未创建,才进行同步。
这样一来,只有第一次会同步,这正是我们想要的。
public class MyClass {
// 静态的本类成员变量
private volatile static MyClass myClass;
// 私有的构造器
private MyClass() {
}
// 像外界提供的静态方法
public static MyClass getInstance() {
if (myClass == null) {
synchronized (MyClass.class) {
if (myClass == null) {
myClass = new MyClass();
}
}
}
return myClass;
}
}
volatile 关键字保证线程的并发下myClass能够强制刷新内存,通知其余线程对象发生的改变。
synchronized 在第一次初始化时进行同步,在同步过程中再进行一次判断,从而保证MyClass对象只有一个实例。
四、总结
单例模式有四种常见的形式:
1:懒汉式:简单、经典、线程不安全
2:同步懒汉式:简单、经典、线程安全、性能降低
3:饿汉式:简单、经典、线程安全、但是没有使用延迟实例化
4:双重加锁:相对复杂、线程安全、使用延迟实例化
长路漫漫,菜不是原罪,堕落才是原罪。
我的CSDN:https://blog.csdn.net/wuyangyang_2000
我的简书:https://www.jianshu.com/u/20c2f2c3560a
我的掘金:https://juejin.im/user/58009b94a0bb9f00586bb8a0
我的GitHub:https://github.com/wuyang2000
个人网站:www.xiyangkeji.cn
个人app(茜茜)蒲公英连接:https://www.pgyer.com/KMdT
我的微信公众号:茜洋 (定期推送优质技术文章,欢迎关注)
Android技术交流群:691174792
以上文章均可转载,转载请注明原创。