单例模式的定义:确保一个类只有一个实例,并提供一个全局访问点
经典单例模式
1 | public class Singleton{ |
以上代码在单线程的环境下运行的很好,但是在多线程的情况下,就会出现问题。主要在于getInstance
函数在多线程情况下会出现资源竞争,可以对getInstance
函数变成同步的方法1
2
3
4
5public static synchronized Singleton getInstance(){ //获得单例对象的全局访问点
if(uniqueInstance == null)
uniqueInstance = new Singleton();
return uniqueInstance;
}
但是同步一个方法可能造成程序执行效率降低100倍,而且其实在getInstance
函数中,并不是整一个方法都属于资源竞争的范围,只有uniqueInstance = new Singleton()
语句才是,而且只有在单例对象第一次初始化的时候才会执行该语句,其余的都不会进入到该语句中,所以直接对整一个方法进行同步有点浪费。
可以双重检查加锁,在getInstance
函数中减少同步1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class Singleton{
//volatile 表明该变量是易变的,编译器不要对其进行优化,每一个反问的时候都要在内存中进行读取,不要存放在寄存器中
private static volatile Singleton uniqueInstance;
public static Singleton getInstance(){
if(uniqueInstance == null){
synchronized(Singleton.class){
//在执行到这里面是,并不知道uniqueInstance对象会不会被其他的线程改变,所以需要在检查一下
//以确保在null的情况下才实例化一个对象
//volatile关键字表明每一次对uniqueInstance变量的读取都是直接在内存中读的
if(uniqueInstance == null)
uniqueInstance = new Singleton();
}
}
return uniqueInstance;
}
}
以上代码可以在多线程的情况下保证代码的正确运行同时对程序执行效率没有影响,只有在第一次初始化是才会执行同步方法。
以上的单例程序都是在程序调用getInstance
方法时,才会实例化Singleton对象的,这也就是所谓的”延迟实例化”,这样可以保证资源不被浪费。如果单例对象所占有的资源不大,那么也可以直接的实例化——“急切”实例化。1
2
3
4
5
6
7public class Singleton{
//在类加载的时候直接的实例化,不存在多线程的问题
private static Singleton uniqueInstance = new Singleton();
public static Singleton getInstance(){
return uniqueInstance;
}
}
在这个方法中,我们依赖JVM在加载这个类的时候就直接创建单例实例。JVM确保在任何线程反问uniqueInstance静态变量前,一定先创建此实例。