完美诠释-------单例模式(饿汉式与懒汉式(含双重加锁))与多线程

------引入
我们在使用Windows的时候无法同时打开两个任务管理器,也就是说,它在整个系统中只有一个唯一的一个实例。怎样实现在一个系统中某个类的实例只能唯一存在呢?单例模式就是很好的解决方法。
单例模式的目的是保证一个类仅有一个实例,并提供一个访问它的全局访问点。单例模式包含的角色只有一个,那就是单例类——Singleton。
一.饿汉模式
在这个类被加载时,静态变量instance会被初始化,此时类的私有构造函数会被调用,单例类的唯一实例将被创建。Java语言中单例类的一个重要的特点是类的构造函数是私有的,从而避免外界利用构造函数直接创建出任意多的实例。

//1.为什么加final?
//回答:避免EagerSingleton的子类重写父类方法,破坏单例模式。
public final class EagerSingleton {
    private static EagerSingleton instance = new EagerSingleton(); 
    private EagerSingleton() {
        
    } 
    public static EagerSingleton getInstance() {
        return instance;
    }
}

二.懒汉模式
与饿汉模式相同之处是,懒汉模式单例类的构造函数也是私有的,与饿汉模式单例类不同的是,饿汉模式单例类在第一次被引用时将自己实例化,在懒汉模式单例类被加载时不会将自己实例化。
例1:单线程版


public class Singleton { 
	private static Singleton instance = null; 
	private Singleton() {
	} 
	public static Singleton getInstance() { 
		if (instance == null) { 
			instance = new Singleton(); 
			}
		return instance; 
	} 
}

问题:
例如两个线程同时进入如下代码块,那么会new出两个不同的实例,那就违背了对象只有一个的目的。

例2:多线程版——性能低


public class Singleton {
    private static Singleton instance = null;
    private Singleton() {

    }
    public synchronized static Singleton getInstance() {
        if (instance == null) {
                instance = new Singleton();
        }
        return instance;
    }
}

问题:
public synchronized static Singleton getInstance()保证了线程安全性;
但是只有在第一次执行此方法时,才真正需要同步,
所以一旦初始化完毕后,就每次调用这个方法,同步都是多余的。增加了开销。

例3:多线程版——二次判断——性能高


public class Singleton {
    private volatile static Singleton instance = null;
    private Singleton() {

    }
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

注意:
首先会检查实例是否创建,如果没有,才进行同步,保证只有在第一次时会同步。避免了开销。

volatile关键字的作用:修饰的共享变量,可以保证可见性,部分保证顺序性。

实例化对象instance = new Singleton();
可以分成三个步骤:
1.分配内存空间
2.初始化对象
3.将对象指向刚分配的内存空间

但是有些编译器为了性能的原因,可能会将第二步和第三步进行重排序,顺序就成了:
1.分配内存空间
2.将对象指向刚分配的内存空间
3.初始化对象

如果顺序1->2->3顺序变为,1->3->2.此时1->3时,对象初始化并未完成,
但是此时另一个线程又判断instance == null,又开始了对象的初始化工作。
使用volatile关键字可以解决在这个问题。

详细再分析饿汉模式:

//1.为什么加final?
//回答:避免EagerSingleton的子类重写父类方法,破坏单例模式。
//2.如果实现了序列化接口,还要做什么来防止反序列化破坏单例?
//回答:反序列化会生成新对象,新生成的对象与之前单例模式生成的对象相比就是不同的对象,这样明显破坏了单例模式。增加方法在下面.
public final class EagerSingleton implements Serializable{
	//问题:为什么加上static?
	//静态成员变量的创建在类初始化过程中创建,由JVM保证线程安全
    private static final EagerSingleton instance = new EagerSingleton(); 
    //3.为什么设置为私有?是否能够防止反射创建新的实例?
    //回答:非私有状态下,其它类也可以创建对象,就不能保证单例。
    //回答:不能。反射可以得到构造器对象。它可以设置setSerializable属性为true。然后调用构造方法创建新的实例。
    private EagerSingleton() {
        
    } 
    public static EagerSingleton getInstance() {
        return instance;
    }
    //作用:在反序列化过程中,一旦发现readResovle()方法返回了instance对象,就不会采用反序列化时字节码生成的对象,它将这个方法返回的对象当做反序列化的结果。
    public Object readResovle(){
		return instance;
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值