什么是单例模式?
单例模式是一种对象创建模式,用于产生对象的一个具体实例,并且可以确保系统中一个类只能产生唯一的一个实例。
这样的方式能够带来两大好处:
- 对于频繁使用的对象,可以省略创建对象所花费的时间,对于复制的对象,这样的创建方式的开销是十分值得的;
- new 的次数少了,对内存的使用频率也就下降了,当然也就减轻了 GC 的压力。
设计思想
这种模式要解决的问题就是要保证应用中只存在一个对象。那怎样实现呢?
- 不允许其他程序new对象
- 在该类中创建对象
- 对外需要暴露一个方法,让其他获取这个对象
体现到代码中,解决办法为:
- 私有化构造函数
- 在本类中创建一个本类对象
- 定义一个方法,能够让其他的类获取这个对象
Code
1. 最简单的写法为
public class Singleton {
private Singleton(){
}
private static Singleton instance = new Singleton();
public static Singleton getInstance(){
return instance;
}
}
这种方式称为饿汉式。
- 优点
- 简单明了
- 在类加载时即完成了实例化,避免了线程同步的问题
- 缺点
- 没有使用懒加载
- 可能造成内存浪费
2. 变种方式
public class Singleton {
private static Singleton instance;
private Singleton(){
}
static {
instance = new Singleton();
}
public static Singleton getInstance(){
return instance;
}
}
相比于第一种写法,本类中将实例化对象的代码放到了 static 代码块中。两种方法的效果其实是一样的。所以优缺点也是一样的。
3. 懒汉式(线程不安全)
public class Singleton {
private static Singleton instance;
private Singleton (){
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
- 优点
- 使用了懒加载
- 缺点
- 当有多个线程并行调用
getInstance()
的时候,就会创建多个实例。也就是说在多线程下不能正常工作。
- 当有多个线程并行调用
4. 懒汉式(线程安全)
public class Singleton {
private static Singleton instance;
private Singleton (){
}
public static synchronized Singleton getInstance(){
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
- 优点
- 线程安全,解决了多实例问题
- 缺点
- 不高效。因为在任何时候只能有一个线程调用 getInstance() 方 法。但是同步操作只需要在第一次调用时才被需要,即第一次创建单例实例对象时。
5. 双重校验锁
public class Singleton {
private static Singleton instance = null;
private Singleton(){
}
public static Singleton getInstance(){
if (instance == null){
synchronized (Singleton.class){
if (instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
进行两次 if(instance == null)
(Double-Check)的检查可以保证线程安全。
- 优点
- 并发度高,提升了性能。
- 缺点
- 并不是绝对安全
6. 内部类
public class Singleton{
private Singleton() {
}
private static class SingletonHolder{
private static Singleton instance=new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
}
这是Google公司的工程师Bob Lee写的新的懒汉单例模式。
- 优点
- 使用JVM本身机制保证了线程安全问题
- 由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的。
- 同时读取实例的时候不会进行同步,没有性能缺陷。
- 不依赖jdk版本。
这种方式也是《Effective Java》推荐的写法。
7. 枚举
public enum EasySingleton{
INSTANCE;
}
- 优点
- 简单明了
- 调用简单,使用EasySingleton.INSTANCE访问实例,比getInstance()简单
- 创建枚举本身就是线程安全的,不用担心存在多个实例的问题
- 缺点
- 枚举类型enum在jdk1.5时才引入,所以这种方法并不适用于jdk1.5之前的版本
测试
使用多线程并发测试。
public class SingletonPatternTest {
public static void main(String[] args){
CountDownLatch latch = new CountDownLatch(1);
int threadCount = 1000;
for (int i = 0; i < threadCount; i++){
new Thread(){
@Override
public void run() {
try {
// all thread to wait
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// test get instance
System.out.println(Singleton.getInstance().hashCode());
}
}.start();
}
// release lock, let all thread excute Singleton.getInstance() at the same time
latch.countDown();
}
}
其中 CountDownLatch latch
为闭锁,所有线程中都用 latch.await()
;等待锁释放,待所有线程初始化完成使用 latch.countDown()
; 释放锁,从而达到线程并发执行 Singleton.getInstance()
的效果。
结果为:
2016228077
2016228077
2016228077
2016228077
...
Reference: