本文主要学习记录自:JavaSE第二季 第30讲 单例设计模式
- 单例设计模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
1、构造方法私有化
2、声明一个本类对象
3、给外部提供一个静态方法获取对象实例
-
两种实现方式
- 1、饿汉式
- 2、懒汉式
eg:
饿汉式单例设计模式
/**
* 饿汉式单例设计模式
*/
public class Singleton1 {
//定义一个本类对象并实例化,饿汉式就是先 new 对象
private static Singleton1 s = new Singleton1();
//构造方法私有化
private Singleton1(){}
public static Singleton1 getInstance(){
return s;
}
public void print(){
System.out.println("饿汉式 -单例设计模式");
}
}
测试:
public class SingletonDemo {
public static void main(String[] args) {
Singleton1 s = Singleton1.getInstance();
s.print();
Singleton1 s1 = Singleton1.getInstance();
System.out.println(s==s1);
}
}
输出:
饿汉式 -单例设计模式
true
懒汉式单例设计模式
/**
* 懒汉式单例设计模式,多线程访问时会有线程访问安全问题
*/
public class Singleton2 {
//定义一个本类对象并实例化
private static Singleton2 s = null;
//构造方法私有化
private Singleton2(){}
public static Singleton2 getInstance(){
if (s==null) {
s = new Singleton2();
}
return s;
}
public void print(){
System.out.println("懒汉式 -单例设计模式");
}
}
以上建议使用饿汉式,比较简单
以上单例代码还有待完善,需要考虑多线程并发时的安全问题,在Java 线程同步与死锁 学习笔记 有详细描述
单例模式改良写法
(摘自: http://dxz.iteye.com/blog/2192679)
懒汉式写法会存在着线程安全问题
public class Singleton{
private static Singleton instance;
//构造函数设置为私有使之不能被外界实例化
private Singleton(){}
//获得实例
public static Singleton getInstance(){
if(instance==null){
synchronized(Singleton.class){
if(instance==null){
instance=new Singleton();
}
}
}
return instance;
}
}
由于乱序执行的优化,导致在程序中执行的过程有时候并不是原子性的,在上面的代码中instance对象的创建就不是原子性的,可以说大部分对象的创建都不是原子性的。
对象的创建过程:
1. 给Singleton的实例分配内存空间
2. 调用Singleton的构造方法进行构造函数初始化
3. 将instance对象指向分配的内存空间(注意到这步instance就非null了)
但是由JVM的乱序执行上面1、2、3的执行顺序2和3并不一定,可能是1、2、3也可能是1、3、2如果是1、2、3还好说并不会出现什么我问题,但是如是执行的顺序是1、3、2那么这样就比较麻烦了,因为在执行到3的时候对象已经是非null了,所以其线程有可能取到被初始化到一半的对象。
改进:使用volatile来阻止程序的乱序执行,因为volatile会给程序添加内存屏障从而阻止编译器或者处理器对代码的乱序执行,从而使双重检验锁在多线程下正确执行。
public static Singleton{
private volatile static Singleton instance;//添加了 volatile
private Singleton(){}
public static Singleton getInstacne(){
if(instance==null){
synchronized(Singleton.class){
if(instance==null){
instance=new Singleton();
}
}
}
return instance;
}
}
但是我们在使用volatile的同时使我们的代码不能被编译器进行代码优化,他需要在本地代码中插入许多的内存屏障指令来保证处理器不发生乱序执行,导致我们的程序在执行的时候变慢
代码优化–线程同步
往往我们会希望实现某一个功能的代码会很少很简洁,这样别人用起来也会很爽
如:
XtionCoreConfig.with(context)
.imageDownloader(new SampleImageDownloader(context,5 * 1000, 30 * 1000))
.config();
有点类似于Builder建造者模式的代码结构
详细实现如下:
public class XtionCoreConfig{
private volatile XtionCoreConfig instance;
private Context context;
//返回单例
public XtionCoreConfig with(Context context){
if(instance == null){
synchronized(XtionCoreConfig.class){
if(instance == null){
instance = new XtionCoreConfig(context);
}
}
}
return instance;
}
private XtionCoreConfig(Context context){
this.context = context;
}
private ImageDownloader imageDownloader;
public XtionCoreConfig imageDownloader(ImageDownloader imageDownloader){
instance.imageDownloader = imageDownloader;
return this;
}
public XtionCoreConfig config(){
XtionWidgetModuleManager.setConfig(context,imageDownloader);
return this;
}
}
代码再优化 – 单例引起的内存泄露
上面的单例代码编写还是有问题的,在上面的例子中,需要持有一个 context 作为成员变量,而 instance做为静态对象,其生命周期要长于长于普通的对象,包括Activity。当系统销毁当前Activity时,当前的Activity被一个单例持有,导致垃圾回收器无法进行回收,进而产生了内存泄露。
解决方法:不持有Activity的引用,而是持有Application的Context引用
private XtionCoreConfig(Context context){
this.context = context.getApplicationContext();
}
深入学习参考资料:
1、比较全面的单例设计模式学习 :单例这种设计模式
2、深入分析Volatile的实现原理
3、知乎:android中用getApplicationContext()会不会避免某些内存泄漏问题? Hewi的回答
4、设计模式之单例模式二(解决无序写入的问题) http://dxz.iteye.com/blog/2192679
5、单例模式及JMM的无序性 http://blog.youkuaiyun.com/hudashi/article/details/6949379