单例模式—读书笔记
单例模式,顾名思义,就是用来创建一个独一无二的,只能有一个实例的对象的入场券。因为在实际应用中,有些对象我们只需要一个,比如说,线程池、缓存、对话框、注册表等。
当然,我们也可以使用全局对象来达到和单例模式一样的作用,但是全局对象有一些缺点,比如说:如果将对象赋值给一个全局变量,那么必须在程序一开始就创建好该对象,如果这个对象很耗资源,但是我们又没有用到它,那就会形成一种浪费。或许我们还可以使用其他方法来达到单例的相同的目的,但是,单例模式很简单,而且经得住时间考验,是值得学习的。
单例模式有很多种实现,下面我们来看一下各种实现的优缺点。
方法1
SingleTon.java
public class SingleTon {
private static SingleTon singleTon;//私有的,不能在类外部访问,只能在类的内部访问
private SingleTon(){
}
public static SingleTon getInstance(){
if (singleTon == null){
singleTon = new SingleTon();
}
return singleTon;
}
}
SingleTon.kt
class SingleTon private constructor(){
companion object {
val instance by lazy { SingleTon() }//用到的时候初始化,且只初始化一次
}
}
总结
单例模式可以保证在任何时刻都只有一个对象(私有构造器)。一般情况下,常常用来管理共享的资源,例如数据库连接和线程池。
方法2:线程安全
上面的java代码在多线程的时候,会出现安全问题。下面我们处理一下多线程的情况:
public class SingleTon{
private static SingleTon singleTon;
private SingleTon(){
}
private static synchronized SingleTon getInstance(){
if (singleTon == null){
singleTon = new SingleTon();
}
return singleTon;
}
}
SingleTon.kt
class SingleTon private constructor(){
companion object {
private var INSTANCE: SingleTon? = null
//在Kotlin中加锁
@Synchronized
fun getInstance(): SingleTon {
if (INSTANCE == null) {
INSTANCE = SingleTon()
}
return INSTANCE!!
}
}
}
在上面的方法中,我们在getInstance()方法中,添加了synchoronized关键字,可以保证同时只有一个线程进入该方法,不会有两个线程可以同时进入这方法。
但是,这个方法有一些缺点,只有第一次执行此方法的时候,才需要同步,换句话说,一旦设置了singleTon对象,就不再需要同步这个方法了。不然的话,性能会很低。
改进
1、如果getInstance()方法的性能对应用程序不是很关键,那就什么都别做。
2、使用“饿汉”创建实例,而不是延迟实例化的做法
public class SingleTon{
private static SingleTon singleTon = new SingleTon();//jvm保证线程安全,且在加载这个类的时候,马上创建此唯一的单例实例。
private SingleTon(){
}
public static SingleTon getInstance(){
return singleTon;
}
}
SingleTon.kt
object SingleTon{
}
3、用“双重检查加锁”,在getInstance()中减少使用同步
使用双重加锁,首先检查是否实例已经创建了,如果尚未创建,“才”进行同步,这样一来,只有第一次需要同步,这正是我们想要的。
public 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;
}
}
volatile 关键字确保,当singleTon变量被初始化成SingleTon实例时,多个线程正确的处理singleTon变量。
SingleTon.kt
class SingleTon private constructor() {
companion object {
val singleton by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
SingleTon()
}
}
}
方法3:静态内部类
SingleTon.java
public class SingleTon{
private SingleTon(){
}
private static class SingleTonHolder{
private static SingleTon singleTon = new SingleTon();
}
private static SingleTon getInstance(){
return SingleTonHolder.singleTon;
}
}
静态内部类的优点是:外部类加载时,不需要立即加载内部类,内部类不加载则不去初始化singleTon,因而不占用内存。只有当getInstance()方法第一次被调用时,才会去初始化singleTon,第一个调用getInstance方法会导致虚拟机加载SingleTonHolder这个类,这种方法不仅能确保线程安全,也能保证单例的唯一性,而且这做到了延迟单例的实例化。
在singleTon创建的过程中,是怎么保证线程安全的呢?如果多个线程同时初始化一个类,那么虚拟机会保证只会有一个线程去初始化这个类,其他线程都需要阻塞等待,直到初始化完毕。因此静态内部类的形式的单例可以保证线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。
SingleTon.kt
class SingleTon private constructor(){
companion object {//静态方法
val instance = Holder.holder
}
private object Holder {//私有静态内部类
val holder= SingleTon()
}
}