Java单例模式
2016-06-23 13:53
351 查看
1、 什么是单例
单例,就是在应用中,只有一个实例(对象)。在什么情况下,需要用到单例呢?比如工具类、线程池、缓存,数据库,账户登录系统、配置文件等程序中可能只允许我们创建一个对象,一方面如果创建多个对象可能引起程序的错误,另一方面创建多个对象也造成资源的浪费。
2、单例的设计思想
单例的关键,就是保证应用只有一个实例,那么如果保证呢?通过以下3点,就可以保证:1、不允许其他程序新建(new)对象;
2、在该类中创建一个对象;
3、对外提供一个可以获取该对象的方法。
以上3点,转换成代码描述,即:
1、该类的构造函数为private;
2、在该类中new一个本来对象;
3、提供static方法,获取new的对象。
3、单例的写法
单例的写法有5种: 1、恶汉式; 2、懒汉式; 3、双重校验锁; 4、静态内部类; 5、枚举。
3.1恶汉式 (线程安全,可用)
恶汉式的写法如下所示:public class Singleton1 { private static Singleton1 instance = new Singleton1(); private Singleton1() {} public static Singleton1 getInstance() { return instance; } }
或者使用静态初始化:
public class Singleton1 { private static Singleton1 instance = null; static { instance = new Singleton1(); } private Singleton1() {} public static Singleton1 getInstance() { return instance; } }
优点:这种方式在加载类的时候,就完成了实例化,避免线程同步问题。
缺点:由于在类加载的时候就实例化了,所以没有达到Lazy Loading(懒加载)的效果,也就是说可能我没有用到这个实例,但是它也会加载,会造成内存的浪费(但是这个浪费可以忽略,所以这种方式也是推荐使用的)。
3.2 懒汉式(线程不安全,不可用)
懒汉式的写法,如下:public class Singleton2 { private static Singleton2 instance; private Singleton2() {} public static Singleton2 getInstance() { if(instance == null) instance = new Singleton2(); return instance; } }
这种写法,在调用getInstance方法时,才会创建对象,所以称为懒汉式。
这种写法很常见,但这种写法线程不安全。
比如有2个线程同时调用这个单例,第一个线程运行到“if(instance == null)”这条语句后阻塞了,这时还没有运行“instance = new Singleton2()”,所以instance还是为null。
第二个线程也调用getInstance方法获取单例,因为instance为null,所以会新建一个对象。
回到第一个线程,是执行了“if(instance == null)”语句阻塞的,接下来执行“instance = new Singleton2()”,又新建了一个对象,把单例对象instance改变了。
所以,这种写法是线程不安全的。
既然线程不安全,那加一个同步就可以了。这样就线程安全了。写法如下:
public class Singleton2 { private static Singleton2 instance; private Singleton2() {} public static synchronized Singleton2 getInstance() { if(instance == null) instance = new Singleton2(); return instance; } }
这种写法虽然是线程安全的,但是每一次调用getInstance方法,都会进行同步。而真正需要同步的,只是第一次新建instance对象时。之后调用,都是直接返回instance对象就可以了。
所以这种写法的效率是很低的,不推荐使用。
既然需要同步的是新建instance对象,那么把同步放在新建instance对象上就好了。于是得到了下面这种写法:
public class Singleton2 { private static Singleton2 instance; private Singleton2() {} public static Singleton2 getInstance() { if(instance == null) synchronized(Singleton2.class) { instance = new Singleton2(); } return instance; } }
这种写法,和懒汉式中的第一种写法一样,也是线程不安全,原因也是一样。
如果有2个线程调用getInstance方法。2个线程都执行完了“if(instance == null)”这条语句,即使对新建instance对象进行了同步,2个线程还是都会新建instance对象。
如果对这种写法,稍微改造一下。就得到了线程安全的“双重校验锁”写法。
3.3 双重校验锁(线程安全,可用)
双重校验锁写法如下:public class Singleton2 { private volatile static Singleton2 instance; private Singleton2() {} public static Singleton2 getInstance() { if(instance == null) synchronized(Singleton2.class) { if(instance == null) instance = new Singleton2(); } return instance; } }
这种写法是线程安全的,比如:
2个线程都执行完了“if(instance == null)”语句,第1个线程先进入同步代码,新建instance对象,这个时候,第2个线程会阻塞。
当第1个线程新建完了instance对象后,第2个线程进入同步代码,再一次进行“if(instance == null)”的判断,这个时候,instance已经被第1个线程创建,所以不为null,不用再次新建。
为什么instance要加volatile修饰,请参考:Java并发编程:volatile关键字解析
3.4 静态内部类(线程安全,可用)
静态内部类的写法如下:public class Singleton3 { private Singleton3() {} private static class SingletonHolder { private static Singleton3 instance = new Singleton3(); } public static Singleton3 getInstance() { return SingletonHolder.instance; } }
这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonHolder类,从而完成Singleton的实例化。类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
优点:避免了线程不安全,延迟加载,效率高。
3.5 枚举(线程安全,可用)
枚举的写法如下:public enum Singleton4 { instance; private Singleton4() {} public void func() {} }
访问方式如下:
Singleton4.instance.func();
可以看到枚举的书写非常简单,访问也很简单在这里SingletonEnum.instance这里的instance即为SingletonEnum类型的引用所以得到它就可以调用枚举中的方法了。借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。可能是因为枚举在JDK1.5中才添加,所以在实际项目开发中,很少见人这么写过,这种方式也是最好的一种方式,如果在开发中JDK满足要求的情况下建议使用这种方式。
总结
在真正的项目开发中一般采用3.1、3.3、3.4、3.5看你最喜欢哪种写法了,我们经常用的Android-Universal-Image-Loader这个开源项目也是采用的3.3这种写法,其实最安全的写法是3.5即枚举,它的实现非常简单而且最安全可谓很完美,但是可能是因为只支持JDK1.5吧又或者是因为枚举大家不熟悉所以目前使用的人并不多,但是大家可以尝试下。另外当我们使用反射机制时可能不能保证实例的唯一性,但是枚举始终可以保证唯一性,具体请参考次博客:http://blog.csdn.net/java2000_net/article/details/3983958但是一般情况下很少遇到这种情况。最后,就是谢谢前辈们。
这篇文章,其实相当于照抄
http://blog.csdn.net/dmk877/article/details/50311791
之所要照着写一遍,就是想动手写一写,这样比单独看的效果要好。
相关文章推荐
- java对世界各个时区(TimeZone)的通用转换处理方法(转载)
- java-注解annotation
- java-模拟tomcat服务器
- java-用HttpURLConnection发送Http请求.
- java-WEB中的监听器Lisener
- Android IPC进程间通讯机制
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- 介绍一款信息管理系统的开源框架---jeecg
- 聚类算法之kmeans算法java版本
- java实现 PageRank算法
- PropertyChangeListener简单理解
- c++11 + SDL2 + ffmpeg +OpenAL + java = Android播放器
- 插入排序
- 冒泡排序
- 堆排序
- 快速排序