设计模式之单例模式
设计模式在我们写代码的过程中不断地被应用,它的存在提高了我们的 代码的健壮性和鲁棒性,那么我们就来学习一下设计模式吧
首先我们从最简单的单例模式开始一起学习,什么是单例模式?单例模式的应用场景?
- 什么是单例模式:当一个类只有一个实例化对象的时候,我们就需要用到单例模式了,这样可以节省资源,提高效率。
- 单例模式的应用场景:例如我们win电脑上的任务管理器,垃圾回收站,数据库连接的时候的数据库连接池,这些功能设计的时候都是应用了单例设计模式。
单例模式分为懒汉式和饿汉式
- 懒汉式:懒汉式是在第一次调用的时候就实例化自己,简单的理解就是,我很懒,你想要调用我一次把我激活了我才给你干活。
- 饿汉式:在类加载的时候就已经实例化了,
那我们就一起来看看怎么实现懒汉式和饿汉式怎么实现的:
- 懒汉式实现代码:
public class Singleton {
private Singleton(){
}
private static Singleton singleton = null;
//静态工厂类线程不安全
public static Singleton getInstance(){
if (singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
这是最简单的懒汉式的单例模式的写法,但是这样的写法不安全,我们再来看看另外一种线程安全的写法:
public class Singleton{
private Singleton singleton = null;
private Singleton(){
}
/**
添加同步锁
*/
public static synchronized getInstance(){
if (singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
为了保证线程安全,我们给getInstance方法添加了同步锁,这样虽然保证了线程安全,但是每次实都需要去同步,非常消耗资源,毕竟大多数情况下是不需要进行同步的
再来看看下面这种实现方式实现单例模式,使用双重锁锁定:
public class Singleton{
private Singleton singleton = null;
private Singleton getInstance(){
}
public static getInstance(){
if (singleton == null){
synchronized (Singleton.class){
if (singleton == null){
singleton = new Singleton();
}
}
}
}
}
使用双重锁来实现在getInstance()方法中做了两次非空判断,这样确保了第一次调用的时候才会去同步,这样既保证了线程安全,也避免了每次都去同步的性能损耗。
第三种方式利用java自带的类加载机制,这种方式既保证了线程安全,也没有性能上的损耗,推荐使用这种方式来实现懒汉式的单例模式
public class Singleton{
private static class LazyHolder{
private static final Singleton INSTANCE = new Singleton();
}
private Singleton(){
}
public static final Singleton getInstance(){
return LazyHolder.INSTANCE;
}
}
- 饿汉式代码:
public class Singleton{
private Singleton(){
}
//在类创建的同时,已经创建好对象,以后不再改变,所以天生就是线程安全的
private static final Singleton single = new Singleton();
public static Single getInstance(){
return single;
}
}
饿汉式在初始化的回收时,已经自行实例化,他天生就是线程安全的
总结
- 线程安全:
饿汉式:饿汉式天生就是线程安全的,所以在多线程中我们还是推荐使用饿汉式的方式来实现单例模式
懒汉式:懒汉式本身是非线程安全的,但是我们可以通过加同步锁的方式来实现线程安全,上面我们有三种方式来实现线程安全的机制,并且对三种机制的性能都做出了解释,
- 性能
饿汉式:饿汉式在类创建的时候就实例化一个对象,不管制后会不会使用这个对象,都会占用一定的内存,但是,在第一次调用的时候也比懒汉式要快,因为资源已经初始化完成了。
懒汉式:由名生意,懒汉式会延迟加载,在第一次使用该单例之后才会实例化对象出来,第一次调用要做初始化,性能上要比饿汉式要差一些,但是在之后再次调用就和饿汉式一样了,因为都已经加载完了