懒汉模式
class Singleton2 {
private static Singleton2 instances = new Singleton2();
public static synchronized Singleton2 getInstances() {
return instances;
}
private Singleton2() {
}
}
这种写法能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是,遗憾的是,效率很低,99%情况下不需要同步。
饿汉模式
//饿汉
class Singleton2 {
private static Singleton2 instances = new Singleton2();
public static synchronized Singleton2 getInstances() {
return instances;
}
private Singleton2() {
}
}
//这种方式基于classloder机制避免了多线程的同步问题,instance在类装载时就实例化.
目前java单例是指一个虚拟机的范围,因为装载类的功能是虚拟机的,所以一个虚拟机在通过自己的ClassLoader装载饿汉式实现单例类的时候就会创建一个类的实例。这就意味着一个虚拟机里面有很多ClassLoader,而这些classloader都能装载某个类的话,就算这个类是单例,也能产生很多实例。
静态内部类
//静态内部类
class Singleton4 {
public static int a = 10;
private Singleton4() {
System.out.println("Singleton4 Constructor");
}
public static final Singleton4 getInstance() {
System.out.println("Singleton4.getInstance()");
return SingletonHolder.INSTANCE;
}
private static class SingletonHolder {
private static final Singleton4 INSTANCE = new Singleton4();
//延迟加载
//内部类默认不会被classLoader加载, 只有调用Singleton4.getInstance时才会加载.
public SingletonHolder() {
System.out.println("SingletonHolder constructor");
}
static {
System.out.println("SingletonHolder static container");
}
}
}
这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。想象一下,如果实例化instance很消耗资源,我想让他延迟加载!
双重检查
//双重检查
class Singleton3 {
private static volatile Singleton3 instances = null;
public static Singleton3 getInstances() {
if (instances != null) {
return instances;
} else {
synchronized (Singleton3.class) {
instances = new Singleton3();
}
}
return instances;
}
private Singleton3() {
}
}
这样方式实现线程安全地创建实例,而又不会对性能造成太大影响。它只是第一次创建实例的时候同步,以后就不需要同步了。
备注:
由于volatile关键字屏蔽了虚拟机中一些必要的代码优化,所以运行效率并不是很高,因此建议没有特别的需要不要使用。双重检验锁方式的单例不建议大量使用,根据情况决定。
volatile笔记:
volatile确保所有线程看见相同的值,不允许保持volatile变量到本地副本.
valatile只是在线程和内存之间同步变量的值,速度快,代价地.,而同步方法则需要线程所有变量与内存之间提供同步,代价更高
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
volatile只保证 并发的可见性,不保证操作的原子性(如自增操作)
volatile会保证有序性. volatile语句前及后的语句可能会重排, 但volatile前的语句不能重排的v之后.[原理是汇编是加入lock前缀,相当于内存栅栏,内存栅栏钱的数据会立即存入主存,]
注意问题:
1.如果单例由不同的类装载器装入,那便有可能存在多个单例类的实例。假定不是远端存取,例如一些servlet容器对每个servlet使用完全不同的类装载器,这样的话如果有两个servlet访问一个单例类,它们就都会有各自的实例。
2.如果Singleton实现了java.io.Serializable接口,那么这个类的实例就可能被序列化和复原。不管怎样,如果你序列化一个单例类的对象,接下来复原多个那个对象,那你就会有多个单例类的实例。
对第一个问题修复的办法是:
private static Class getClass(String classname)
throws ClassNotFoundException {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
if(classLoader == null)
classLoader = Singleton.class.getClassLoader();
return (classLoader.loadClass(classname));
}
}
对第二个问题修复的办法是:
public class Singleton implements java.io.Serializable {
public static Singleton INSTANCE = new Singleton();
protected Singleton() {
}
private Object readResolve() {
return INSTANCE;
}
}