单例模式的几种形式

一、概述

        在Android中我们用到最多的设计模式应该就是单例模式了,单例模式也是最简单的一种设计模式,如果对象在应用中是全局唯一的,那我们就可以使用单例模式,在Android系统中,也大量用到了单例模式。那么,单例模式有哪些写法呢?每种写法的优缺点又是哪些呢?

二、单例模式的写法

1、饿汉式

饿汉式是最简单的单例模式定义方法,首先我们需要将类的构造方法定义为private,这样在外部就不能通过构造函数生成对象;其次,我们需要定义一个静态的对象并用构造方法对其赋值;最后,提供一个public方法访问我们定义的静态对象。具体代码如下:

public class SingleTonOne {
    private final static SingleTonOne singleTonOne = new SingleTonOne();

    private SingleTonOne() {}

    public static SingleTonOne getInstance() {
        return singleTonOne;
    }
}
优点:简单方便

缺点:不管程序中是否使用到了单例对象,都会生成单例对象,并且由于静态对象是在类加载时就需要生成,会降低应用的启动速度

适用:类对象功能简单,占用内存较小,使用频繁

不适用:类对象功能复杂,占用内存大,使用概率较低


2、懒汉式

懒汉式相比于饿汉式,最大的区别在于静态对象的生成是在使用到单例实例的时候才去生成的,而不是在应用启动的时候构造,如果应用中没有使用到单例对象,是不会构造单例对象的。具体代码如下:

public class SingleTonTwo {
    private static SingleTonTwo singleTonTwo = null;

    private SingleTonTwo() {}

    public static SingleTonTwo getInstance() {
        if (singleTonTwo == null) { // 首次生成单例对象时,如果多个线程同时调用getInstance方法,这个条件针对多个线程同时成立,那么就会生成多个单例对象
            singleTonTwo = new SingleTonTwo();
        }
        return singleTonTwo;
    }
}
这种单例模式的定义使用的是懒加载的方式,即需要的时候才去构造单例对象,针对于单线程模式,这样定义是没有问题的,但是如果是多线程模式,就有可能构造多个单例对象,如果多个线程同时首次调用getInstance去获得单例对象,在判断单例对象是否为空时可能都会成立,那样就会构造多个单例对象,懒汉式的定义方式最大的问题就是它是线程不安全的。

优点:单例对象的生成是在应用需要使用单例对象时才去构造,可以提高应用的启动速度

缺点:不是线程安全的,如果多个线程同时调用getInstance方法,那么可能会生成多个单例对象

适用:单例对象功能复杂,占用内存大,对应用的启动速度有要求

不适用:多线程同时使用


3、懒汉式同步锁

懒汉式单例模式的问题在于它是线程不安全的,那么如果我们加上线程保护机制,问题不就解决了吗?懒汉式同步锁就是在懒汉式上加上线程同步锁,已确保线程安全。具体代码如下:

public class SingleTonThree {
    private static SingleTonThree singleTonThree = null;

    private SingleTonThree() {

    }

    public static SingleTonThree getInstance() {
        synchronized(SingleTonThree.class) { // 加上线程同步锁,确保每次只有一个线程进入
            if (singleTonThree == null) {
                singleTonThree = new SingleTonThree();
            }
        }
        return singleTonThree;
    }
}

4、双重校验锁

用懒汉式同步锁可以解决线程安全问题,但是我们在获得单例对象时有必要每次都判断线程同步锁吗?如果单例对象已经不为空了,我们直接返回单例对象就行了,就不需要判断线程同步锁和构造单例对象了,双重校验锁是在懒汉式同步锁的基础上再加上一层单例对象是否为空的判断,以减少判断线程同步锁的次数,从而提高效率,代码如下:

public class SingleTonFour {
    private volatile static SingleTonFour singleTonFour = null; // 加上volatile关键字,线程每次使用到被volatile关键字修饰的变量时,都会去堆里拿最新的数据
   private SingleTonFour() {

    }

    public static SingleTonFour getInstance() {
        if (singleTonFour == null) { // 在懒汉式同步锁的基础上加上了一个判断,如果单例对象不为空,就不需要执行获得对象同步锁的代码,从而提高效率
            synchronized (SingleTonFour.class) { // 只有当单例对象为空时才会执行
                if (singleTonFour == null) {
                    singleTonFour = new SingleTonFour();
                }
            }
        }
        return singleTonFour;
    }
}

优点:懒加载、线程安全、效率高

缺点:代码比较复杂


5、利用静态内部类

java的静态内部类的加载是在使用到该静态内部类的时候才去加载的,并且加载静态内部类是线程安全的,我们可以利用这一特性来定义单例模式,具体代码如下:

public class SingleTonFive {
    /**
     * 私有静态内部类,程序只有当使用到静态内部类是才会去加载静态内部类,然后生成单例对象
     */
    private static class SingleTonFiveHolder {
        public static SingleTonFive singleTonFive = new SingleTonFive();
    }

    private SingleTonFive() {

    }

    /**
     * 只用调用了getInstance方法,程序中使用到了静态内部类SingleTonFiveHolder,才会去加载SingleTonFiveHolder,生成单例对象
     * @return
     */
    public SingleTonFive getInstance() {
        return SingleTonFiveHolder.singleTonFive;
    }
}
优点:实现简单,懒加载,线程安全

缺点:增加了一个静态内部类,apk文件增大

6、枚举

package com.liunian.androidbasic.designpattern.singleton;

/**
 * Created by dell on 2018/4/19.
 * 枚举单例模式
 * 优点:线程安全,不用担心序列化和反射问题
 * 缺点:枚举占用的内存会多一点
 */

public enum  SingleTonSix {
    INSTANCE;

    private String field;

    public String getField() {
        return field;
    }

    public void setField(String field) {
        this.field = field;
    }
}

优点:线程安全,不用担心序列化和反射问题

缺点:枚举占用的内存会多一点,至于为什么枚举占用内存会比较多,可以参考:https://blog.csdn.net/xiao_nian/article/details/80002101

三、总结

单例模式的定义有饿汉式、懒汉式、懒汉式同步锁、双重校验锁、静态内部类、枚举这几种定义方式,每种方式都有他们优缺点,我们可以根据自己的需要去选择自己的定义方式。

猜你喜欢

转载自blog.csdn.net/xiao_nian/article/details/79999953
今日推荐