本文学习一个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,则要重新实例化。
好了,就写到这里,希望您能有所收获,有啥问题,可以私信或回复。