单例模式说白来了就是代码你无论怎样调用,就只有一个对象,但引用可以有很多个。根据代码的不同,单例模式又分为懒汉模式和饿汉模式。
单例模式最基本:因为只有一个实例,肯定不能被外部类所实例化,所以构造方法必须私有化。即:
public class Singleton {
private Singleton() {
...
}
}
懒汉模式:
是在你真正用到的时候才去实例化单例对象(以时间换空间)。
1、普通懒汉模式:
public class Singleton {
private static Singleton singleton = null;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
这种方式存在线程不安全的问题,解决的办法就是使用synchronized 关键字,便是单例模式的第二种写法了。
2、加类锁的懒汉模式:
public class Singleton {
private static Singleton singleton = null;
private Singleton() {
}
public synchronized static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
或者
public class Singleton {
private static Singleton singleton = null;
private Singleton() {
}
public static Singleton getInstance() {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
return singleton;
}
}
这两个写法是一样的,这样写保证不会出线程问题。但是它的缺点在于每次调用getInstance()都进行同步,造成了不必要的同步开销。这种模式一般不建议使用。为了不让每一步都进行同步操作,于是就多了了一个判断为空。这便是懒汉模式的第三种写法。
3、 判空之后加类锁的懒汉模式
public class Singleton {
private static Singleton singleton = null;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
这种写法看似完美,但从原子性的方面来看,有一个非常隐秘的问题。实例化原子操作:
(a)给Singleton的实例分配内存
(b)调用Singleton()的构造函数,初始化成员字段;
(c)将singleton对象指向分配的内存空间(即singleton不为空了);
但是由于Java编译器允许处理器乱序执行,以及在jdk1.5之前,JMM(Java Memory Model:java内存模型)中Cache、寄存器、到主内存的回写顺序规定,上面的b、c的执行顺序是不能保证。执行顺序可能是a-b-c,也可能是a-c-b,如果是a-c-b顺序,并且恰恰在c执行完毕,b尚未执行时(这时候引用已经不为空,指向分配的内存,但内存中什么都没有),被切换到线程B中,这时候因为singleton在线程A中执行了步骤c了,已经非空了,所以,线程B直接就取走了singleton,再使用时就会出错。报不报错主要看运气(虽然报错的概率极低)。对其进行修改:
public class Singleton {
private static volatile Singleton singleton = null;
private Singleton() {
}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
在声明中的volatile保证了顺序性。volatile关键字会强制将修改的值立即写入主存,当分配完内存,实例化的值会立即被写入到主存中,然后引用再指向内存空间。当然volatile或多或少的有一些效率问题。懒汉模式到这里就结束了。
饿汉模式:
是在不管你用不用,一开始就创建单例对象,这里的一开始,指的是该类加载的时候(以空间换时间)
1、 普通的饿汉模式
public class Singleton {
private static Singleton singleton = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return singleton;
}
}
这种饿汉模式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生线程安全。
2、 具有饿汉模式特点的懒汉模式:
public class Singleton {
private Singleton() {
}
private static class SingletonHolder {
static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(int i1, String s1) {
return SingletonHolder.INSTANCE;
}
}
普通的饿汉模式是只要Singleton类被装载了,那么instance就会被实例化,而这种方式是Singleton类被装载了,instance不一定被初始化。
单例模式到这就这些了,我建议再看单例模式的时候去看一下synchronized。