Java设计模式1:单例模式

咳咳,想系统的整理一下知识想了好久了,毕竟工作了快半年了,业务代码感觉已经写得差不多了,明显感觉到又到了再夯实一遍基础的时候了,毕竟基础打得好后面才能得心应手,事半功倍。所以就从设计模式这里开始看吧。

 

设计模式感觉在写代码的时候也是挺重要的,确实有些时候就是不知道该如何设计自己的代码,所以这次就从这里入手啦。话虽如此我也不打算全写,就挑着常用的来写吧,感觉全都写了还是有点多的。。。

 

啰嗦了一大片,现在就从最常用的单例模式开始写吧。

 

单例模式是什么

简单来说单例模式就是一种类的写法,它保证了你所写的类最多只会被实例化一个对象。

 

使用场景

单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的场合适合使用,如多个模块使用同一个数据源连接对象等等。如: 

    1.需要频繁实例化然后销毁的对象。 

    2.创建对象时耗时过多或者耗资源过多,但又经常用到的对象。 

    3.有状态的工具类对象。 

    4.频繁访问数据库或文件的对象。 

以下都是单例模式的经典使用场景: 

    1.资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。 

    2.控制资源的情况下,方便资源之间的互相通信。如线程池等。 

 

实现方法

首先说一下所有写法的共通之处,也是单例模式要想保证唯一对象并正常使用的必要写法:

1.构造器私有

2.自己创建自己的实例化对象

3.提供给外界获得此对象的方法

通过构造器私有,我们的类将不能够被外界实例化,同时外界只能通过本类提供的方法来获取由本类已经创建好的对象。

 

首先是最常见的两种方法,懒汉式和饿汉式:

 

首先是饿汉式:

 

/** 饿汉式*/

public class Singleton {

    private static Singleton singleton = new Singleton();

    private Singleton(){}

    public static Singleton getInstance(){

        return singleton;

    }

    public void sayHello(){

        System.out.println("hello world !");

    }

}

 

接下来是懒汉式:

 

/** 懒汉式*/

public class Singleton{

    private static Singleton singleton = null;

    private Singleton(){}

    public static Singleton getInstance() {

        if (singleton==null){

            singleton=new Singleton();

        }

        return singleton;

    }

}

 

这两个我们对比来看,主要区别就在于new Singleton()这一步执行的时机,饿汉式在初始化系统变量的时候就进行了new这一步动作,而当需要的时候直接返回已有的对象;反过来看懒汉式则并没有在初始化系统变量的时候创建对象,而是在调用的时候进行判断,如果还没有对象,就new一个出来,如果有就直接返回去,因此懒汉式总是在第一次调用的时候创建对象,以后一直返回这个对象。

 

这两种方法各有各的侧重点,饿汉式由于是在加载类的时候进行的对象创建,但是该对象可能在接下来的一段时间内并没有使用,所以会造成资源的浪费。而懒汉式在使用的时候进行创建,也就保证了不会白白浪费掉,但是创建对象毕竟会花费时间,所以又回到了计算机的一个永恒不变的话题,是时间换空间,还是空间换时间?

 

除了对于空间和时间的倾向性不同,还有一个问题需要我们考虑,那就是线程安全的问题。

 

对于饿汉式来说,由于虚拟机在加载类的时候会自动保证只有一个线程来处理,所以饿汉式是线程安全的;但是对于懒汉式来说,有可能出现在调用时,当第一个线程判断为空,进入了创建对象的步骤,这时候线程切换,第二个线程也运行到这里,由于上一个线程还没能创建出来对象,所以第二个线程也判断为空,进入了创建线程的操作,这样便会导致我们的单例不再”单例“。

 

既然知道了症结在哪里,那我们就对症下药就好了,我们知道通过synchronized关键字可以进行线程的同步,所以我们在可能产生问题的部分,也就是为空判断上加上线程同步:

/**

 * 懒汉式(加线程同步)

 */

public class Singleton {

    private static Singleton singleton = null;

    private Singleton() {}

    public static Singleton getInstance() {

        synchronized (Singleton.class) {

            if (singleton == null) {

                singleton = new Singleton();

            }

            return singleton;

        }

    }

}

从而保证了懒汉式的单一实例。

 

但是问题又来了,众所周知的线程同步会导致效率低下,那么有没有办法提高一下效率呢?通过研究之前的代码我们发现,所有想要使用该类的对象的地方都要通过getInstance()方法,而每次执行该方法都会直接进入synchronized片段,所以我们可以在该片段之外再加上一层判断,判断该类的实例是否已经初始化成功

/**

 * 懒汉式(双重加锁线程同步)

 */

public class Singleton {

    private static Singleton singleton = null;

    private Singleton() {}

    public static Singleton getSingleton() {

        if (singleton == null) {

            synchronized (Singleton.class) {

                if (singleton == null) {

                    singleton = new Singleton();

                }

                return singleton;

            }

        }

        return singleton;

    }

}

这样在懒加载的时候我们终于能够保证线程同步了,真的不容易。。。。

 

好了,上面介绍完麻烦的方法,我们来介绍一个非常巧妙的方法

 

/** 内部类方式*/

public class Singleton {

    private Singleton(){}

    private static class SingletonHolder {

        private static final Singleton instance=new Singleton();

    }

    public static Singleton getInstance(){

        return SingletonHolder.instance;

    }

    public void sayHello(){

        System.out.println("hello world !");

    }

}

 

我们可以看到在我们要实现单例的类内部实现了一个内部类,然后通过内部类来持有我们的实例对象。这种方法的巧妙之处就在于将饿汉式和懒汉式的优点都结合起来了,由于我们的内部类是在调用的时候才进行加载的,所以拥有懒汉式节省资源的好处,同时由于我们的实例对象是在加载内部类的过程中实例化的,又会有虚拟机级别的线程安全保证,可以说是一举两得了。

 

除了这种方法以外,由于Java在jdk1.5之后添加了枚举类型,可以说枚举类型的特性和单例模式有异曲同工之妙了,我们可以通过构建一个只有一个实例的枚举类型来实现单例模式:

/** 内部类方式*/

public enum Singleton {

    INSTANCE;

    public void sayHello(){

        System.out.println("hello world !");

    }

}

 

只需要在枚举的时候只枚举一个实例,单例模式就自动完成了!简单到爆炸QWQ

猜你喜欢

转载自blog.csdn.net/pikapikapikapika/article/details/84872413
今日推荐