单例模式要点有三个:1、类只能有一个实例;2、它必须自行创建这个实例;3、它必须自行向整个系统提供这个实例。
从具体实现角度来说,就是这三点:1、单例模式的类只提供私有的构造函数,2、类定义中含有一个该类的静态私有对象,3、该类提供了一个静态的公有的方法用于创建或获取它本身的静态私有对象。
我这里要介绍的是五种单例模式的写法,分别是:懒汉,双重检验锁,饿汉,静态内部类,枚举。
懒汉单利模式
public class Singleton1 {
//1、私有构造方法
private Singleton1(){
}
//2、定义一个含有该类的静态私有对象
private static Singleton1 singleton1=null;
//3、提供一个静态的公有方法,用于创建或获取它本身的静态私有对象
public static Singleton1 getInstance(){
if(singleton1==null){
singleton1=new Singleton1();
}
return singleton1;
}
}
这个代码有一个致命的问题,当有多个线程并行调用getInstance()时,就会创建多个实例。下面是一个多线程的测试例子:
import java.util.concurrent.CountDownLatch;
public class Test {
public static void main(String[] args) {
final CountDownLatch latch = new CountDownLatch(1);
int threadCount = 10;
for (int i = 0; i < threadCount; i++) {
new Thread() {
public void run() {
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Singleton1.getInstance().hashCode());
}
}.start();
}
latch.countDown();
}
}
测试结果
我们发现存在了两个不同的对象。
为了解决上面的问题,简单的处理方法是将getInstance()方法设为同步(synchronized)
public class Singleton1 {
//1、私有构造方法
private Singleton1(){
}
//2、定义一个含有该类的静态私有对象
private static Singleton1 singleton1=null;
//3、提供一个静态的公有方法,用于创建或获取它本身的静态私有对象
public static synchronized Singleton1 getInstance(){
if(singleton1==null){
singleton1=new Singleton1();
}
return singleton1;
}
}
测试结果:
虽然这个解决多线程出现多实例问题,但是它并不高效,因为每次调用getInstance()都要检查同步,影响性能,毕竟大多数是不用同步检查的。于是就有了双重检验锁。
双重检验锁单例模式
public class Singleton1 {
//1、私有构造方法
private Singleton1(){
}
//2、定义一个含有该类的静态私有对象
private static Singleton1 singleton1=null;
//3、提供一个静态的公有方法,用于创建或获取它本身的静态私有对象
public static Singleton1 getInstance(){
if(singleton1==null){
synchronized (Singleton1.class) {
if(singleton1==null){
singleton1=new Singleton1();
}
}
}
return singleton1;
}
}
当Singleton1 已经被实例化后,以后每次调用getInstance(),都会在第一次判断不为空直接return,不会进入检查同步。那么为什么里面也要加一个判空?设想一下这样的情景,Singleton1还没有被实例化,有两个线程同时调用getInstance(),因为Singleton1此时没有被实例化,所以这两个线程都能通过第一个判空,然后进入同步锁,假如线程1获得锁,进入第二个判空,并且实例化Singleton1,然后释放了锁。注意,这个时候的Singleton1已经实例化了,它不为空了。第二个线程获得锁,进入方法里面,如果不加判空,则线程2也会重新实例化Singleton1,这就出现了两个该对象的实例化。加上判空后,因为此时Singleton1不为空,所以就不会再次实例化该对象。
当然这还是有一点缺陷的,因为singleton1=new Singleton1();
这句不是原子操作,这里我就不做详细说明,处理的办法就是将singleton1变量声明成volatile就可以了。private volatile static Singleton1 singleton1=null;
饿汉单例模式
这种方法非常简单,在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快,而且本身就是线程安全的。
public class Singleton2 {
private Singleton2(){
}
private static final Singleton2 singleton2=new Singleton2();
public static Singleton2 getInstance(){
return singleton2;
}
}
静态内部类单例模式
public class Singleton3 {
private Singleton3(){
}
private static class Singleton{
private static final Singleton3 singleton3=new Singleton3();
}
public static Singleton3 getInstance(){
return Singleton.singleton3;
}
}
由于Singleton是内部私有的,只有通过getInstance()访问,因此它也是懒汉式;同时它也是线程安全的,没有同步问题,没有性能缺陷。
枚举单例模式
public enum Singleton4 {
INSTANCE;
private Singleton4(){
System.out.println("---初始化单例---");
}
}
通过Singleton4 s=Singleton4.INSTANCE;
进行测试,测试结果如下:
枚举单例有序列化和线程安全的保证,而且只要几行代码就能实现是单例,简单又安全。