目录
1、饿汉式
public class ImageLoader{
private static ImageLoader instance = new ImageLoader;
private ImageLoader(){}
public static ImageLoader getInstance(){
return instance;
}
}
饿汉式顾名思义,就是这个汉子很饿,一上来就把单例对象创建出来了,要用的时候直接返回即可,这种可以说是单例模式中最简单的一种实现方式。但是问题也比较明显。单例在还没有使用到的时候,初始化就已经完成了。也就是说,如果程序从头到位都没用使用这个单例的话,单例的对象还是会创建。这就造成了不必要的资源浪费。所以不推荐这种实现方式。
2、懒汉式
public class ImageLoader{
private static ImageLoader instance;
private ImageLoader(){}
public static synchronized ImageLoader getInstance(){
if(instance == null){
instance = new ImageLoader();
}
return instance;
}
}
懒汉式也顾名思义,就是这个汉子比较懒,一开始的时候什么也不做,知道要使用的时候采取创建实例的对象。看起来还不错,只有在使用实例的时候,我们才回去创建对象。但是细心的同学可能发现了,我们在获取实例的方法上加了锁,避免多线程引发的创建多个单例的情况。多线程的问题是避免了,但也造成了整体性能的下降,每次使用单例对象,都需要锁判断,降低了整体性能。很明显,懒汉式也不是我们所要追求的目标。
3、Double CheckLock(双重锁判断机制)
public class ImageLoader{
private static ImageLoader instance;
private ImageLoader(){}
public static ImageLoader getInstance(){
if(instance == null){ ①
synchronized (ImageLoader.class){
if(instance == null){
instance = new ImageLoader();
}
}
}
return instance;
}
}
第一次访问,实例化与懒汉式相同,需要加锁;之后(第二、三…次)的访问,不需要加锁,提高效率。
可以看到,在获取单例对象的时候,我们先进行了两为空判断,并且在第二次判断前加了锁,这就让程序变得更加优秀,在使用的时候,只会前几次获取单例对象的时候会进行锁判断,一旦单例对象创建完成,锁的任务也就完成了,在懒汉式的基础上,提高了性能。DCL是使用最多的单例实现方式,能够在使用的时候才进行单例对象的初始化创建,并且能够在绝大多数情况下保证对象的唯一性的正确性。请注意,是绝大多数情况下,也就是说,这种模式也不能完全保证单例的对象的完美实现,但是,就一般情况下,这种模式都能满足需求。
存在的问题是无序性。new操作是无序的(指令重排),它可能会被编译成:
-
先分配内存,让instance指向这块内存
-
在内存中创建对象
然而我们需要意识到这么一个问题,synchronized虽然是互斥的,但不代表一次就把整个过程执行完,它在中间是可能释放时间片的,时间片不是锁。也就是说可能在a执行完后,时间片被释放,线程2执行到①,这时他读到的instance是不是null呢?
基于可见性,可能是null,也可能不是null。
给instance 加上volatile(保证可见性和禁止指令重排)可以解决无序性
public volatile static ImageLoader instance;
要区分jdk版本,
在jdk1.4及之前,volatile并不能保证new操作的有序性,但是它能保证可见性,因此标记1处,读到的不是null,导致了问题。
从1.5开始,加了volatile关键字的引用,它的初始化就不能是:
-
先分配内存,让instance指向这块内存
-
在内存中创建对象
而应该是:
-
在内存中创建对象
-
让instance指向这个对象.
这种形式,也就避免了无序性问题。
4、静态内部类
public class ImageLoader {
private static class InnerInstance {
private static final ImageLoader instance = new ImageLoader();
}
private ImageLoader() {
}
public static ImageLoader getInstance() {
return InnerInstance.instance;
}
}
这种方式,并未加锁,因为第一次加载ImageLoader类时,并不会实例化单例对象,只有第一次调用getInstance()方法时会导致虚拟机加载InnerInstance类,这种方式不仅能保证对象的单一性,还避免加锁带来的性能问题,又启动了延迟加载的优化,所以这就是单例模式的终极实现版本,也是推荐使用的方式。
利用了classloader的机制来保证初始化instance时只有一个线程。
5、枚举
class Resource {
}
public enum SomeThing {
INSTANCE;
private Resource instance;
SomeThing() {
instance = new Resource();
}
public Resource getInstance() {
return instance;
}
}
在枚举中我们明确了构造方法限制为私有,在我们访问枚举实例时会执行构造方法,同时每个枚举实例都是static final类型的,也就表明只能被实例化一次。在调用构造方法时,我们的单例被实例化。也就是说,因为enum中的实例被保证只会被实例化一次,所以我们的INSTANCE也被保证实例化一次。