单例模式
来源:互联网 发布:c语言 char 中文 编辑:程序博客网 时间:2024/05/18 13:08
单例模式属于创建型模式。可以保证一个类仅有一个实例,并提供一个访问它的全局访问点。
优点:
1)在内存中只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例
2)避免对资源的多重占用
饿汉式:
class Singleton { //类内部实例化一个实例 private static Singleton instance = new Singleton(); //私有构造方法避免被外部使用 private Singleton(){} //对外提供获取实例的方法 public static Singleton getInstance(){ return instance; }}
饿汉式单例,在类被加载的时候对象就会被实例化,会造成不必要的损耗。如果这个类被多次调用会造成多次实例化。
有两种解决方法,使用静态内部类,或者使用懒汉式
静态内部类:
class Singleton1 { private static class SingleInner { private static Singleton1 instance = new Singleton1(); } private Singleton1() { } public static Singleton1 getInstance() { return SingleInner.instance; }}
懒汉式:
class Singleton2 { private static Singleton2 instance; private Singleton2() { } public static Singleton2 getInstance() { if (instance == null) { instance = new Singleton2(); } return instance; }}
懒汉式单例会存在线程安全问题,在多线程情况下,有可能两个线程同时进入if语句,这样,在两个线程都从if退出时,就创建了两个不一样的对象。
线程安全的懒汉式
class Singleton3 { private static Singleton3 instance; private Singleton3() { } public static synchronized Singleton3 getInstance() { if (instance == null) { instance = new Singleton3(); } return instance; }}
这种方法,一定程度上保证了线程安全,但是效率低。
双重检验锁
class Singleton4 { private static Singleton4 instance; private Singleton4() { } public static Singleton4 getInstance() { if (instance == null) { synchronized(Singleton4.class){ if (instance == null) { instance = new Singleton4(); } } } return instance; }}
上面的例子,通过减小锁的范围,大大提升了效率。通过两重if(instance==null) ,第一重保证只有intance 为null 时,才能进入synchronized代码段;第二重防止出现多个实例。
但仍有小概率出现问题。在解决问题之前,首先说明两个概念:1)原子操作 2)指令重排
原子操作
即不可分割的操作,在计算机中,就是指不会因为线程调度被打断的操作。比如简单的赋值是一个原子操作
m=6 //这是一个原子操作
假如m原先的值为0 ,那么对于这个操作,要么执行成功m变成了6,要么是没执行m还是0,而不会出现m=其他值这种中间态,即使是在并发的线程中
声明并赋值不是一个原子操作
int m=6 //不是原子操作
对于这个语句,至少需要两个操作。
1)声明一个变量n
2)给n赋值为6
这样就会有一个中间状态:变量n已经被声明还没有被赋值的状态。这样在多线程中,由于线程执行顺序的不确定性,如果两个线程都是用m,就有可能导致不稳定的结果出现。
指令重排
计算机为了提高效率,会做一些优化,在不影响最终结果的情况下,可能会对一些语句的执行顺序进行调整。
比如:
int a; //语句1
a=8;//语句2
int b=9;//语句3
int c = a+b;//语句4
正常来说,对于顺序结构,执行的顺序是自上而下,即1234。
但是,由于指令重排的原因,因为不影响最终结果,所以执行顺序可能会变成3124或1324.
由于语句3,4.非原子性操作,3和4也可能会拆分成原子操作,再重排。
也就是说,对于非原子性操作,在不影响最终结果的情况下,其拆分成的原子操作可能会被重新排序执行顺序。
在上面例子里,instance = new Singleton() 不是一个原子操作。JVM 大概做了下面3件事。
1,给instance 分配内存
2,调用Singleton 的构造函数来初始化成员变量,形成实例
3,将instance 对象指向分配的内存空间(执行完此步,instance 非空)
由于JVM的即时编译器中存在指令重排的优化,则第2和第3步的顺序不能保证,若线程1 访问时,第3步先执行,则另一个线程2访问时,instance 非空。直接返回instance,就会出现问题。
问题在于:线程1 对instance的写操作没有完成,线程2就执行了读操作
使用 volatile
class Singleton5 { private static volatile Singleton5 instance; private Singleton5() { } public static Singleton5 getInstance() { if (instance == null) { synchronized(Singleton5.class){ if (instance == null) { instance = new Singleton5(); } } } return instance; }}
volatile 关键字一个作用是禁止指令重排,把instance声明为volatile.对它的写操作就会有一个内存屏障,这样在它的赋值完成之前,就不会调用读操作。
枚举实现
enum Singleton6{ INSTSNCE; public void run(){ }}//调用Singleton6.INSTSNCE.run();
序列化
序列化可能会破坏单例,要想防止序列化对单例的破坏,需要在Singleton类中定义readResolve 方法。
class Singleton7 { private static volatile Singleton7 instance; private Singleton7() { } public static Singleton7 getInstance() { if (instance == null) { synchronized(Singleton7.class){ if (instance == null) { instance = new Singleton7(); } } } return instance; } private Object readResolve(){ return instance; }}
- 单例、单例模式
- 单例模式-多线程单例模式
- 单件模式(单例模式)
- 设计模式------单例模式
- 设计模式------单例模式
- 设计模式-单例模式
- 设计模式 - 单例模式
- 设计模式---单例模式
- 设计模式---单例模式
- PHP模式-单例模式
- 【设计模式】单例模式
- 设计模式-单例模式
- 设计模式----单例模式
- 设计模式--单例模式
- 设计模式-单例模式
- 单例模式(单子模式)
- 设计模式-单例模式
- [设计模式] 单例模式
- [C][C++]长度、大小等的详解:sizeof, strlen, size...
- 使用Kotlin开发Android应用
- lnmp环境搭建好后,,,,解析php文件报错 502
- maven新建后没有src/main/java解决
- svn中文件冲突
- 单例模式
- Redis 3.0 Windows 安装步骤
- Java构造函数
- 生活游记——泰国自由行
- 使用ffmpeg 解码mp4文件的时候出现 Error splitting the input into NAL units.
- linux 默认打开index.php
- 语法分析注意点
- 双机热备Nginx+Keepalived搭建HA高可用负载均衡环境
- Quartz源码解析 ---- 触发器按时启动原理