Java单例

来源:互联网 发布:新概念英语配音软件 编辑:程序博客网 时间:2024/06/18 09:19

Java单例模式

任何一个系统的大部分配置文件读取、线程安全类工具类、常量类等都会使用单例模式---不要说你没见过,有可能见过但是知道是单例模式,还有你确定你懂单例模式吗?你有梦想吗?哈哈,我想当架构师,我想当CTO,我想当一名技术极客----狗屁!!!先醒一醒,你凭什么能成功,靠嘴皮子,还是靠意淫?
  1. 单例其实是分成了如下的四种方式
  2. 单例—懒汉模式
  3. 单例—饿汗模式
  4. *单例—双重检查
  5. 单例–基于类初始化

什么是单例模式?简单来说有如下几个特点:
a)在同一个进程中只有一个该类的对象
b) 只能通过公开的静态方法获取对象实例
c) 其它任何地方都不能new的方式获取实例

饿汉模式

①构造防范私有化
②在私有成员变量中new出来
③公开方法返回实例化的成员变量

public class HungrySingleton {    private HungrySingleton(){}//①    private static HungrySingleton instance = new HungrySingleton();//②    public static HungrySingleton getInstance(){//③        return instance;    }}

构造方法私有化保证这个类只能在当前类使用new
私有成员【类本身】直接通过new初始化–饿的表现在成员变量的时候就new
外部如何使用,类名.公开方法

缺点:还没有使用就new了,这个如果是在你的应用启动的过程中会减慢你应用重启速度的。

懒汉模式

①构造防范私有化
②在私有成员变量中置为空 = null
③公开方法返回实例化的成员变量

public class LazySingleton {    private LazySingleton(){}//①    private static LazySingleton instance = null;//②    public static synchronized LazySingleton getInstance(){//③        if(instance == null){//④            instance = new LazySingleton();//⑤        }        return instance;    }}

构造方法私有化保证这个类只能在当前类使用new
私有成员【类本身】=null;–懒的表现,到了使用的时候在new
公开的方法上加上synchronized

缺点:保证了单例,却损失了性能。
直接说下公开方法,
I:)如果没有synchronized关键字来修饰方法,那么多个线程(可以理解成多个方法、或者同一个方法同时调用这个LazySingleton.getInstance())调用时,会出现new多次的情况:
补充说明一下:new是一个复合操作(不是一个原子操作),instance = new LazySingleton();分成了三个步骤:
1.分配内存(Java堆开辟一个小的空间—堆?多大?)
2.初始化成员变量
3.将对象指向变量instance
更隐藏的一个问题是:2和3顺序是不一定的(这点很重要!)
再回到多个线程调用没有使用synchronized修饰的getInstance方法上,假设线程A能执行到方法的④处,线程b执行到方法⑤处,这个时候就悲剧了,线程B执行了new一次,线程A有执行了一次new,这在同一个进程中就不止new一个实例了。
II:)有了synchronized关键字来修饰方法,还是假设有多个线程同时调用getInstance方法,此时由于加上了synchronized,同时只能有一个线程能够进入到方法内部,线程a执行的时候,线程b由于获取不到锁,在方法外边排队。a执行完毕,线程b在进入方法的时候由于a已经new了将对象指向了instance,此时线程b的instance = null不成立,直接return instance了。保证了获取一个实例。但是有个问题,这个时候访问LazySingleton.getInstance的时候成了串行的,这个高并发场景下是带了性能的急剧下降,显然是不合适的。

双重检查单例模式

public class DoubleCheckSingleton {    private DoubleCheckSingleton(){}//①    private static volatile DoubleCheckSingleton instance = null;//②    public static DoubleCheckSingleton getInstatnce(){//③        if(instance == null){//④            synchronized(DoubleCheckSingleton.class){//⑤                if (instance == null) {//⑥                    instance = new DoubleCheckSingleton();//⑦                }            }        }        return instance;//⑧    }}

构造方法私有化保证这个类只能在当前类使用new
私有成员【类本身】=null;但是此时加上了一个修饰符volatile,很重要!!
公开的方法上加上去掉了synchronized,在方法体内使用代码块锁此时多个线程都可以直接进入到方法内判断instance是否为空,不为空直接返回。
方法体内④⑤⑥和②处的volatile联合起来就是所说的双重检查。
下面来分析一下:
a)首先方法上没有synchronized来修饰,多个线程可以同时进入到方法体内,假设线程A判断instance==null成立,则进入synchronized代码块内,其他线程都在synchronized外,这个时候线程A将synchronized的代码⑤⑥⑦都执行完,然后返回了。此时其他线程进入到synchronized内再次判断一下instance==null不成立直接返回。在有线程进入到方法的时候④就直接返回instance,满足了同一个进程中只有一个DoubleCheckSingleton对象实例。
b)分析到这里还有volatile关键字,它在JMM(Java内存模型中)有两个作用:
I)禁止指令重排序
II)一个线程的对一个变量的修改对其他线程可见
c)如果没有volatile关键字修饰你可能获取到一个没有被初始化的对象,也就是
懒汉模式中nen对象三部中的2和3颠倒的情况,指令重排序了。有了volatile来修饰自然就不会重排序了。

缺点:jdk1.4之前对volatile的支持不算完全,

JMM的核心就是变量的修改何时可以对其他线程可见,详细了解可以参考我的这篇文章:volatile在JMM中的作用

volatile在这里的所用就是禁止指令重排序,上面分析过new一个对象是复合操作可以重排序的。
这里说的一个线程修改了一个volatile修饰的变量,可以立即对其他线程可见。其实是有个条件的就是复合操作中不能存在数据依赖。
volatile是怎么做到禁止指令重排序的?happyBefore/内存屏障
如果想更清楚的知道synchronized和volatile的同学以及JMM的一些概念:指令重排序,数据依赖,轻量级锁等可以参考我的博文:分分钟钟让你知道JMM的几个概念。

基于类初始化单例模式

public class JvmSingleton {    private JvmSingleton(){}//①    //外部类作为内部类静态的成员属性     private static class InnerSingletonHolder{//②        private static JvmSingleton instance = new JvmSingleton();//③    }    public static JvmSingleton getInstance(){        return InnerSingletonHolder.instance;//④    }}

构造方法私有化
声明一个私有静态内部类,内部包含一个静态私有外部类的成员new出来了
公开方法getInstance返回通过内部类.外部类属性

为什么内部类是static的,内部类重点的成员属性是static的?
a)如果不是static内部类,那么公开方法getInstance获取内部类需要new,问题其实又回到了原点:保证只是new一个对象的问题上了,这个是怎么保证内部类是new一次哈!

为什么②和③能保证单例?
都知道static的修饰的成员内存中仅有一份
static可以直接通过类.静态方法或者类名.静态成员
这两个操作都是在new对象之前完成的。拓展一下:
在一个new之前JVM要做一些事情(一个类的生命周期是分成了7个步骤的,new之前的步骤如下):
1.类加载—将class文件加载(读)到内存中。
2.连接(验证、准备、解析)
3.初始化
在2的准备阶段在内存的方法区就为static的成员赋上默认值(如果是final static的变量直接附上初始值),这里的默认值根据类型而定 对象的是null,int是0以此类推。在第三步赋上new JvmSingleton的真实值。
又由于它是static的内存中只会有一份。

缺点:?

综上所述:
单例的都需要将构造方法私有化
饿汉模式:没有使用就new会减缓应用启动速度
懒汉模式:加上synchronized会损失性能
双重检查:jdk1.4之前对volatile的支持不完善

推荐使用根据类初始化来获取一个单例。