Java 设计模式之单例模式详解

本文学习一个Java单例模式。

单例模式

单例,顾名思义,就是只存在一个实例。或许,你也会疑问?为什么会使用到单例模式呢?这是因为,很多情况下,我们需要一个实例,比如线程池、缓存、驱动等,如果存在多个实例,那将会导致混乱。

首先我们复习下构造函数,

构造函数

构造函数的用处是实例化对象,
我们的用法通常是这样,

public class ClassA {
    public ClassA() {
    }
}

用到该类的时候,我们可以这样实例化,

ClassA a = new ClassA();

有两个地方需要我们注意,
1. 构造函数名与类名一致
2. 权限修饰符为public
关于上述两条,第1条,类名一致是Java规定的,那有没有想过第2条,权限修饰符必须是public么?答案是不是,我们可以将其修改为private,但是这样就会带来另一个问题,由于是private,在其他类中,无法调用该函数,所以就无法实例化,那么如何实例化呢?

既然构造函数无法访问,那么我们能不能通过其他函数来得到该类的实例呢?答案是可以的。

public class ClassA {
    private ClassA() {
    }

    public static ClassA getClassA(){
        return new ClassA();
    }
}

上面代码中,提供了一个getClassA方法,该方法的返回类型是ClassA,函数的主体是返回一个ClassA的实例,注意到该函数是静态函数(static),为什么是静态函数呢?静态函数与非静态函数的最主要区别在于,静态函数可以直接通过类名来调用,而非静态函数必须通过对象实例化来引用。
然后,我们可以这样来获取ClassA实例,

ClassA a = ClassA.getClassA();

那么,我们将其修改成下面代码,就成为了单例模式,

单例1

public class Singleton {
    private static Singleton uniqueInstance;

    private Singleton() {
    }

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

首先声明一个实例uniqueInstance,在getInstance方法中,判断uniqueInstance是否为null,若为null,则实例化并赋值,若不为null,则直接返回。该方法保证了uniqueInstance为唯一的实例。
然而,在多线程下,该方法却会出现问题,因为,如果线程1和线程2同时调用该函数,将会出现同时修改现象。在操作系统中,这属于多线程同步问题,我们可以将其获取实例方法定义为同步方法,就可以避免该现象。

单例2

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

synchronized 关键字的意思是,同步。意味着,在同一时刻,只能有一个对象调用该方法,其他调用方法处于等待状态,直到调用结束,其他调用方法才会一一执行(同步)。这样就保证了不会同时修改对象。
然而,该方法也存在一个缺点,那就是如果很多个地方都需要调用getInstance方法的话,那样JVM的压力很大,因为它要保证这么多个调用都是同步调用(一个一个顺序执行),同时,也会导致多个线程在getInstance方法上等待,这样显然带来了较大的性能影响。我们可以使用方法3和方法4来改进.

单例3

我们将代码修改成下面这样,

public class Singleton {
    private static Singleton uniqueInstance = new Singleton();

    private Singleton() {
    }

    public static synchronized Singleton getInstance() {
        return uniqueInstance;
    }
}

和单例2最主要的区别,在于uniqueInstance唯一实例,是在加载类的时候创建的(static关键字造成的),这样就可以保证,在所有调用getInstance之前,uniqueInstance已被初始化,该方法也不会给JVM带来额外的负担。

单例4

我们将代码修改为,

public class Singleton {
    private volatile static Singleton uniqueInstance;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (uniqueInstance == null) {
            synchronized (Singleton.class) {//同步代码块
                if (uniqueInstance == null)
                    uniqueInstance = new Singleton();
            }//同步代码块结束
        }
        return uniqueInstance;
    }
}

与单例2的区别在于,synchronized 关键字不是作用在getInstance方法上,这样调用getInstance方法并不会给JVM带来负担(JVM并不需要对线程调用进行调整,进行顺序执行)。
当多个线程调用getInstance方法时,该方法并不会阻塞,而是会在代码块上进行阻塞。在同步代码块内,为什么要再次检查是否为空呢?设想以下情形,10个线程在实例为null的情况下同时调用getInstance方法,那么只会有一个线程(姑且叫做线程A)能得到Singleton的锁,其他线程都会在同步代码块上阻塞,当线程A执行完毕后,此时uniqueInstance应该不为null,但是也不能确保其一定不是null,如果线程A没有正常执行完毕,就被中断了呢?所以我们在同步代码块里需要再次判断一下uniqueInstance 是否为null,若不为null,直接返回就是,若为null,则要重新实例化。

好了,就写到这里,希望您能有所收获,有啥问题,可以私信或回复。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值