浅谈Java单例模式
来源:互联网 发布:程序员岗位 编辑:程序博客网 时间:2024/05/22 03:47
相信在设计模式中有一个经常提到的概念:单例模式,为什么它经常出现在面试话题中,因为它的应用场景十分广泛。
使用场景:
比如
- 数据库连接池,作为数据库的缓存,避免频繁连接关闭数据库,
- Java线程池,控制管理线程。
- log4j日志记录,由始至终记录着运行日志。
定义:
保证系统中一个类只有一个实例,而且必须自己创建自己的唯一实例,该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。
创建单例模式的几种方式以及比较
1. 饿汉模式
/* 1. 饿汉模式: 2. 优点:线程安全 3. 缺陷:性能低/加载类就初始化单例/不适合需要外部传入参数配置的单例模式 */public class SingletonHungry { private static final SingletonHungry instance = new SingletonHungry(); public static SingletonHungry getInstance() { return instance; } public static void main(String[] args) { SingletonHungry s1 = SingletonHungry.getInstance(); SingletonHungry s2 = SingletonHungry.getInstance(); System.out.print("饿汉模式实例对比:"); //true System.out.println(s1.getInstance()==s2.getInstance()); } }
由于饿汉模式在类内部创建实例,所以它是线程安全,正式它在类内部就静态加载,所以它不能从外部传入参数配置。
具体来看看懒汉模式.
2. 懒汉模式
package com.dd.code.singleton;/* * 懒汉模式 * 优点:简单/对比饿汉,加载此单例可以外部传入配置 * 缺陷:线程不安全 */public class SingletonLazy { private static SingletonLazy instance; /* * 配置成员conf(假设必须传入conf该单例才可以加载) * 这不能在类中优先初始化 private static final SingletonHungry instance = new SingletonHungry(); */ private static String conf; //外部传入属性配置 public static void setConf(String conf) { SingletonLazy.conf = conf; } public static SingletonLazy getInstance() { if (instance == null) { instance = new SingletonLazy(); } return instance; } public static void main(String[] args) { SingletonLazy.setConf("配置文件优先"); SingletonLazy s1 = SingletonLazy.getInstance(); SingletonLazy s2 = SingletonLazy.getInstance(); System.out.print("懒汉模式实例对比:"); //true System.out.println(s1.getInstance()==s2.getInstance()); }}
对比饿汉模式,懒汉模式可以传入必要配置再手动实例化,但是由于手动实例化,则需要考虑线程安全问题。
3. 线程安全懒汉模式
/* * 线程安全懒汉加载 * 优点:线程安全/可以外部传配置 * 缺陷:代价较高,创建单例只需要第一次保证线程安全就好,不需要每次都同步 * 优化解决:SingletonDoubleCheck */public class SingletonThreadSafe { private static SingletonThreadSafe instance; //加了同步关键字synchronized private synchronized SingletonThreadSafe getInstance() { if (instance == null) { instance = new SingletonThreadSafe(); } return instance; } public static void main(String[] args) { SingletonThreadSafe s1 = new SingletonThreadSafe(); SingletonThreadSafe s2 = new SingletonThreadSafe(); System.out.print("线程安全懒汉加载实例对比:"); //true System.out.println(s1.getInstance()==s2.getInstance()); }}
加了同步关键字synchronized保证了线程安全,但是它的性能就降低了,而且其实创建单例只需要第一次保证线程安全就好,不需要每次都同步。
所以引入了新的优化,好像很厉害的双重锁检测模式
4. 双重锁检测单例
/* * 双重锁单例模式(较复杂) * 优点:可传入配置/对比SingletonThreadSafe性能优化,只有在实例为空(第一次实现同步) * 为什么要第二次判断instance是否为空,因为把synchronized放里层的话, * 有可能有多个线程进入了*临界区*,synchronized只能保证临界区每次由一个线程执行而已, * 二次检测可以让其他线程下次不初始化,防止冗余情况 */public class SingletonDoubleCheck { /* * volatile关键字: * 要知道,instance = new SingletonDoubleCheck();不是原子性操作, * 虽然volatile关键字不能保证原子性,但是可以禁止指令重排 * 保证了instance = new SingletonDoubleCheck() 这一行的有效执行顺序 */ private volatile static SingletonDoubleCheck instance; private static SingletonDoubleCheck getInstance() { if (instance == null) { //非临界区 synchronized (SingletonDoubleCheck.class) { //*临界区* if (instance == null) { instance = new SingletonDoubleCheck(); } } } return instance; } public static void main(String[] args) { SingletonDoubleCheck s1,s2; s1 = SingletonDoubleCheck.getInstance(); s2 = SingletonDoubleCheck.getInstance(); System.out.print("双重锁单例加载实例对比:"); //true System.out.println(s1==s2); }}
双重锁模式可以说是性能和线程安全的折中,它保证线程安全又保证不需要每次都控制同步,第一个判断if (instance == null)用来拦截已经创建的线程。
主要复杂的是从非临界区到临界区的情况,即当未创建实例的时候:要知道synchronized关键字其实只能保证每次一个线程执行修饰代码块,并不能保证只有一个线程,假设有超过一个线程进入临界区,此时如果线程一执行instance = new SingletonDoubleCheck(),则线程2下一次会根据第二个if (instance == null)判断是否再次创建实例,所以第二个if其实相当于一个flag标记,它巧妙的避免了多个线程创建多个实例。
这种方式有点烧脑,官方推荐还有另一种创建方式,静态内部类模式,比较推荐使用的一种。
5. 静态内部类
/* * 静态内部类 */public class SingletonNested { private static class SingletonHolder{ public static final SingletonNested HOLDER_INSTANCE = new SingletonNested(); } public static SingletonNested getInstance() { return SingletonHolder.HOLDER_INSTANCE; } public static void main(String[] args) { SingletonNested s1, s2; s1 = SingletonNested.getInstance(); s2 = SingletonNested.getInstance(); System.out.print("静态内部类实例对比:"); //true System.out.println(s1==s2); }}
静态内部类创建单例的优点:
- 由jvm保证线程安全,不需要用到同步控制,性能较高;
- 因为区别于懒加载把instance作为静态内部成员,所以类加载时不会实例化instance 只有getInstance调用才会初始化。
参考链接:
http://wuchong.me/blog/2014/08/28/how-to-correctly-write-singleton-pattern/
http://blog.csdn.net/lovelion/article/details/7420888
- Java单例模式浅谈
- 浅谈Java单例模式
- Java单例模式浅谈
- 浅谈java单例模式
- 浅谈Java单例模式
- Java-设计模式-单例模式浅谈
- 浅谈java中的单例模式
- 浅谈单例模式
- 浅谈单例模式
- 单例模式浅谈
- 浅谈单例模式
- 浅谈单例模式
- 单例模式浅谈
- 浅谈单例模式
- 浅谈-单例模式
- 浅谈单例模式
- 浅谈单例模式
- 浅谈单例模式
- LeetCode刷题总结【一】
- poj(2676)——Sudoku
- ListView控件-(ViewHolder)
- ECMAScript 6 入门
- 第五天:反思
- 浅谈Java单例模式
- PHP结合Redis来限制用户或者IP某个时间段内访问的次数
- JDBC实现Oracle数据库的增删改查
- D3 笔记九:制作饼图
- 欢迎使用CSDN-markdown编辑器
- 使用libjpeg进行图片读取
- [Go]程序结构——变量
- 网络编程--ip
- JVM(5) JVM 参数详解