java单例设计模式总结
2016-04-02 12:07
525 查看
说到设计模式,比较常见的是单例设计模式!
单例模式的特点:每一次实例化的对象都是同一个;
单例模式有两种实现方式:1、饿汉式2、懒汉式
饿汉式,就是还没一开始使用便初始化好。
缺点:还没使用便加载好了,占用内存。
如果想等到程序一加载后再初始化,可以使用懒汉式。
缺点:会有线程安全问题。
考虑到线程安全问题,可以使用synchronized关键字修饰。
为了解决如上问题,可以考虑使用一个临时变量引用,来确保先初始化好再将指针指向内存的顺序,代码如下
针对以上问题,终极解决方案有两种:
1、使用volatile
在JDK1.5的时候,出现volatile关键字,在并发的时候,当线程A的数据发生变化时,会通知其他线程修改数据这样能有效的避免JVM编译导致的重排序问题,即使出现线程A还没初始化完对象,线程B就进来的情况,也会通过volatile让线程A初始化好后通知线程B变更数据,代码如下:
2、使用静态匿名内部类
4000
eton被加载的时,内部类不会初始化,只有当getInstance()被调用时才会加载SingletonInstance,由于使用类加载的形式,并用final修饰,初始化对象的时候具有原子性,所以不要考虑线程安全,所以不需要加锁。
备注:通过反射我们可以强制生成多个对象,这些极端现象我们不做考虑,除了这样的方式,我们还可以使用序列化的方式强制生成多个对象,代码如下:
上面的代码页面打印的结果是true,但是注释掉readResolve函数的时候,打印的结果会是false。
大家看完还有什么不懂或是觉得代码哪里有问题,欢迎留言交流。
单例模式的特点:每一次实例化的对象都是同一个;
单例模式有两种实现方式:1、饿汉式2、懒汉式
饿汉式,就是还没一开始使用便初始化好。
public class Singleton { private static final Singleton singleton = new Singleton(); private Singleton(){} public static Singleton getInstance() { return singleton; } }优点:预加载,避免了线程安全问题。
缺点:还没使用便加载好了,占用内存。
如果想等到程序一加载后再初始化,可以使用懒汉式。
public class Singleton { private static Singleton singleton = null; private Singleton(){} public static Singleton getInstance() { if (singleton == null) { singleton = new Singleton(); } return singleton; } }优点:懒加载,一开始不占用资源。
缺点:会有线程安全问题。
考虑到线程安全问题,可以使用synchronized关键字修饰。
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() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }这样从代码的从代码的角度上看就问题,但是从JVM上分析,创建一个对象需要申请分配一块内存初始化构造函数;将指针指向分配的内存。这两个操作的先后顺序,JVM并没有规定好,特别是java编译后会将字节码进行重排序。所以可能会出现这样的情况:线程A先进来创建对象,将指针指向分配的内存,但是未完成初始化完成,线程B进来的时候,由于指针有引用singleton!=null,会直接未完成初始化好的对象,则会出现异常。
为了解决如上问题,可以考虑使用一个临时变量引用,来确保先初始化好再将指针指向内存的顺序,代码如下
public class Singleton { private static Singleton singleton = null; private Singleton(){} public static Singleton getInstance() { if (singleton == null) { Singleton temp; synchronized (Singleton.class) { temp = singleton; if (temp == null) { synchronized(Singleton.class) { if (temp == null) { temp = new Singleton(); } } singleton = temp; } } } return singleton; } }问题其实还没完成解决,因为JVM编译优化可能将改变代码的顺序,如singleto = temp;放到同步代码块里面,所以需要重新做处理。
针对以上问题,终极解决方案有两种:
1、使用volatile
在JDK1.5的时候,出现volatile关键字,在并发的时候,当线程A的数据发生变化时,会通知其他线程修改数据这样能有效的避免JVM编译导致的重排序问题,即使出现线程A还没初始化完对象,线程B就进来的情况,也会通过volatile让线程A初始化好后通知线程B变更数据,代码如下:
public class Singleton { private volatile static Singleton singleton = null; private Singleton(){} public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
2、使用静态匿名内部类
public class Singleton { private Singleton(){} private static class SingletonInstance { private static final Singleton singleton = new Singleton(); } public static Singleton getInstance() { return SingletonInstance.singleton; } }由于代码没有static的属性,所以当Singl
4000
eton被加载的时,内部类不会初始化,只有当getInstance()被调用时才会加载SingletonInstance,由于使用类加载的形式,并用final修饰,初始化对象的时候具有原子性,所以不要考虑线程安全,所以不需要加锁。
备注:通过反射我们可以强制生成多个对象,这些极端现象我们不做考虑,除了这样的方式,我们还可以使用序列化的方式强制生成多个对象,代码如下:
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class Singleton implements Serializable { private static final Singleton singleton = new Singleton(); private Singleton(){} public static Singleton getInstance() { return singleton; } private Object readResolve(){ return singleton; } public static void main(String[] args) throws Exception { Singleton s1 = null; Singleton s2 = getInstance(); FileOutputStream fos = new FileOutputStream("d:/aa.txt"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(s2); oos.flush(); oos.close(); FileInputStream fis = new FileInputStream("d:/aa.txt"); ObjectInputStream ois = new ObjectInputStream(fis); s1 = (Singleton) ois.readObject(); System.out.println(s1.hashCode() == s2.hashCode()); } }
上面的代码页面打印的结果是true,但是注释掉readResolve函数的时候,打印的结果会是false。
大家看完还有什么不懂或是觉得代码哪里有问题,欢迎留言交流。
相关文章推荐
- java实现归并排序
- java 二进制数字符串转换工具类
- java 二进制数字符串转换工具类
- java线程的创建
- java多线程--多线程基础小结
- JDK环境变量解析
- eclipse快捷键以及使用技巧大全 (其中shift+ctrl+F 格式化 会和搜狗输入法冲突,把输入法这个关了)
- Java中byte与16进制字符串的互相转换
- Eclipse下debug时候提示找不到源文件(edit the source lookup path)
- java语言所用的运算符注意点
- JDK安装错误
- JAVA字符串格式化-String.format()的使用
- Java从入门到精通(一)
- eclipse 测试java项目基础
- Struts2版本升级到struts2 2.3.15.1操作说明
- struts2请求逐渐简化处理方式
- java基础语法注意点归纳总结
- 让eclipse代码编辑器换下皮肤
- Java内存区域分析
- JAVA的静态变量、静态方法、静态类