Java的23种设计模式中,第一种,也是最基础的一种设计模式就是单例模式,在面试中常常会被考到,下面我们开始简单地实现一下几种单例模式的写法。
首先,单例模式需要满足的几个条件:一、只能通过单例类获取实例;二、只能获取一个实例;
根据以上条件,我们很容易就可以写出一个简单的单例实现:
public class Single {
private static Single single = new Single();
private Single()
{}
public static Single getIntance()
{
return single;
}
public void say()
{
System.out.println("single");
}
}
先私有化无参数构造方法,由此我们就可以防止其他对象不通过单例模式私自获取实例化;再写一个静态的单例类,由此可以保证该单例类只会有一个实例,配套的写了一个静态方法,直接返回实例,完成最简单的单例模式。如此简单粗暴的写法叫做饿汉写法,为什么叫做饿汉呢?因为,一旦我们加载了该类,单例类中的单例对象就会被实例化,没有lazyloading的效果。
由上面改进,我们还可以写一个懒汉写法:
public class Single {
private static Single single;
private Single()
{}
public static Single getIntance()
{
if(single == null)
{
single = new Single();
}
return single;
}
public void say()
{
System.out.println("single");
}
}
上诉代码的变化主要体现在,单例的对象不会在类加载初期便被实例化,而是在调用获取实例化的静态方法时才会获取实例化,达到lazyloading的效果。代码上的变动主要体现在:成员变量没有进行实例化,获取实例的静态方法里加了一个是否为null的判断,其他大体无差,对应着饿汉方法,称其为懒汉方法。为什么叫做懒汉呢?因为该方式总是在调用getInstance()方法时才会加载实例,表现得很“懒”。
懒汉方法又存在着一个缺陷,因为饱汉方法总是在类加载初期就拥有了实例化的单例对象,所以“天生的”线程安全,但是懒汉方法就存在线程缺陷,在多线程环境下,如果同时获取了该对象,进行非空判断,很有可能会new两次,获取两个实例化对象。
于是,我们需要写一个线程安全版本的懒汉模式,这个倒也简单,只要在方法上加上同步锁关键字就行了:
public class Single {
private static Single single;
private Single()
{}
public static synchronized Single getIntance()
{
if(single == null)
{
single = new Single();
}
return single;
}
public void say()
{
System.out.println("single");
}
}
----------2018-06-04更新
饿汉模式是天生具有线程安全性的,因为饿汉模式是在类加载的初始化阶段就把实例化对象引用交给了single。在上面关于,线程安全的懒汉模式只写了一种,但是实际上有五种实现方式:
1、如上一种;
2、将synchronized关键字改为修饰代码块(作用不大);
3、内部静态类实现,写一个内部静态类,在其中使用饿汉方法,然后返回内部静态的静态成员变量,同样也是lazy加载;
4、double-check方法,实现思路是:先判断返回引用是否为空,为空则加锁,不为空则直接返回引用,大大地提高了效率,但是存有一个问题。single = new Single()并不是一个原子化的操作,他可以分为三个步骤:
1)在内存中分配一块内存;
2)将内存初始化;
3)最后将引用指向内存;
但是当指令重排时,很可能就不是如上的步骤了,有可能是132。
如此一来假使线程1执行到指令3时,但是未执行指令2,这时线程2执行到外层的非空判断,发现引用不为空,直接跳过first check,返回了一个未初始化的实例,这时,就存在问题了。
所以,使用volatile关键字修饰该静态变量,禁止指令重排序,完美解决问题,代码如下:
//使用volatile关键字防止重排序,因为 new Instance()是一个非原子操作,可能创建一个不完整的实例
private static volatile Singleton3 singleton3;
private Singleton3() {
}
public static Singleton3 getSingleton3() {
// Double-Check idiom
if (singleton3 == null) {
synchronized (Singleton3.class) { // 1
// 只需在第一次创建实例时才同步
if (singleton3 == null) { // 2
singleton3 = new Singleton3(); // 3
}
}
}
return singleton3;
}
5、最后,还有一个方式,使用ThreadLocal实现懒汉,附上我的ThreadLocal的博客
ThreadLocal深入浅出,代码如下:
// ThreadLocal 线程局部变量,将单例instance线程私有化
private static ThreadLocal<Singleton> threadlocal = new ThreadLocal<Singleton>();
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
// 第一次检查:若线程第一次访问,则进入if语句块;否则,若线程已经访问过,则直接返回ThreadLocal中的值
if (threadlocal.get() == null) {
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查:该单例是否被创建
instance = new Singleton();
}
}
threadlocal.set(instance); // 将单例放入ThreadLocal中
}
return threadlocal.get();
}
有点多此一举的意思,还不如直接饿汉。