java 设计模式之单例模式①
官大官小总有烦恼,越升越高没有终了;钱多钱少没法比较,越多越好没完没了;吃好吃赖都是饭菜,占多占少知足就好,放弃计较快乐就到!
设计模式学习,近期我会把23中设计模式都写成博客,敬请期待~
单例模式介绍
所谓单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该对象只能提供一个取得的对象实例方法
单例模式的8种分类
加粗是推荐使用
- 饿汉式(静态常量)
- 饿汉式(静态代码块)
- 懒汉式(线程不安全)
- 懒汉式(线程安全,同步方法,效率低)
- 懒汉式(线程安全,同步代码块)
- 双重检查
- 静态内部类
- 枚举
饿汉式(静态常量)
public class InstanceMode01 {
//私有化构造器 外部不能 new
private InstanceMode01(){
}
//类的内部实现创建对象
private static final InstanceMode01 instance = new InstanceMode01();
//向外暴露一个静态方法
public static InstanceMode01 getInstance(){
return instance;
}
}
- 优点:代码简单,在类装载的时候完成实例化,避免了线程同步
- 缺点:因为在类装载的时候创建,所以没有达到懒加载效果,如果没有用当前实例,可能造成内存浪费
来看看是否实现了单例:
通过对比hashCode()来判断内存地址是否相同(实例相同):
InstanceMode01 instanceMode1 = InstanceMode01.getInstance();
InstanceMode01 instanceMode01 = InstanceMode01.getInstance();
Log.i("单利模式之饿汉静态方法01:",
instanceMode1.hashCode() + "\t" + instanceMode01.hashCode());
效果图(1.1)
:
建议:可以使用,但会造成内存浪费
饿汉式(静态代码块)
public class InstanceMode02 {
//私有化构造器 外部不能 new
private InstanceMode02(){
}
//类的内部实现创建对象
private static final InstanceMode02 instance;
static {
//静态代码块创建单利
instance = new InstanceMode02();
}
//向外暴露一个静态方法
public static InstanceMode02 getInstance(){
return instance;
}
}
优点缺点和饿汉式(静态常量)一样,因为静态内部类会在类加载的时候执行,和饿汉式(静态常量)并没有差别,只是写法不同而已
对比hashCode:
效果图(1.2)
:
建议:可以使用,但会造成内存浪费
懒汉式(线程不安全)
public class InstanceMode03 {
/**
* 私有化构造器 外部不能 new
*/
private InstanceMode03(){
}
private static InstanceMode03 instance;
//提供一个共有方法 使用到时才去创建intance
public static InstanceMode03 getInstance(){
if (instance == null) {
instance = new InstanceMode03();
}
return instance;
}
}
-
优点: 起到了懒加载作用,没有浪费只能在单线程使用!
-
缺点: 线程不安全,多线程不可以使用,当在多线程情况下
假设有2条线程: 线程A走到if(instance == null) 线程B也走到 if(instance == null) 两个线程会同时走instance = new InstanceMode03(); 会创建2个实例,违背了单例模式的原则,所以在多线程的情况下不建议使用!
public static InstanceMode03 getInstance(){
if (instance == null) {
instance = new InstanceMode03();
}
return instance;
}
对比hashCode:
效果图(1.3)
:
不建议使用,在项目开发中多线程是必备使用的!
懒汉式(线程安全,同步方法,效率低)
public class InstanceMode04 {
/**
* 私有化构造器 外部不能 new
*/
private InstanceMode04(){
}
private static InstanceMode04 instance;
//提供一个共有方法 使用到时才去创建intance
public static synchronized InstanceMode04 getInstance(){
if (instance == null) {
instance = new InstanceMode04();
}
return instance;
}
}
-
优点:解决线程安全问题
-
缺点:效率太低
假设我现在要执行100次getInstance() 那么这100次都要进行线程同步,而不是直接返回实例对象 我想要的结果是,如果现在已经存在实例,那么直接return给我实例对象即可 因为是线程同步,所以会排好队一个一个执行,效率低
对比HashCode:
效果图(1.4)
:
不建议使用:效率太低
懒汉式(线程安全,同步代码块)
public class InstanceMode05 {
/**
* 私有化构造器 外部不能 new
*/
private InstanceMode05(){
}
private static InstanceMode05 instance;
//提供一个共有方法 使用到时才去创建intance
public static InstanceMode05 getInstance(){
if (instance == null) {
synchronized (InstanceMode05.class){
instance = new InstanceMode05();
}
}
return instance;
}
}
缺点:
问题缺陷和 InstanceMode03()懒汉式线程不安全 一样
没有解决线程问题,虽然加了代码块
在多线程的情况下,同时获取2个getInstance()时就会创建2个实例
为什么加了synchronized (InstanceMode05.class){}也没有解决线程问题?
public static InstanceMode05 getInstance(){
//1
if (instance == null) {
//2
synchronized (InstanceMode05.class){
//3
instance = new InstanceMode05(); //4
} //5
} //6
return instance;
}
假设:
现在有线程A和线程B,当线程A和线程B同步执行时,当同时走到第2步,因为是同步,所以 if (instance == null) {}都可以执行,走到第3步时,当A先走了,已经创建了实例,现在B还在外面等待,当B走了3之后的步骤,就会创建2个实例,违背了单例的思想.
对比HashCode:
效果图(1.5)
:
不建议使用,线程不安全
双重检查
public class InstanceMode06 {
/**
* 私有化构造器 外部不能 new
*/
private InstanceMode06() {
}
/**
* volatile:防止JVM优化被禁止重排序的。
* 重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。
*/
private static volatile InstanceMode06 instance;
/*1*/
public static InstanceMode06 getInstance() {
/*2*/
if (instance == null) {
/*3*/
synchronized (InstanceMode06.class) {
/*4*/
if (instance == null) {
/*5*/
instance = new InstanceMode06();
}
}
}
return instance;
}
}
volatile关键字:
volatile解决问题:
问题出在第5行:
在new创建变量的时候 instance = new InstanceMode06();
它并不是一个原子操作。事实上,它可以”抽象“为下面几条JVM指令:
memory = allocate(); // 1:分配对象的内存空间
ctorInstance(memory); // 2:初始化对象
instance = memory; // 3:设置instance指向刚分配的内存地址
操作2依赖于操作1,但是操作3并不依赖于操作2
所以JVM可以以“优化”为目的对它们进行重排序,经过重排序后如下:
memory = allocate(); // 1:分配对象的内存空间
instance = memory; // 3:设置instance指向刚分配的内存地址
// 注意,此时memory对象还没有被初始化!
ctorInstance(memory); // 2:初始化对象
这样做有什么区别呢?
正常情况是先分配对象的内存空间,然后初始化,最后instance指向分配的内存地址
现在有可能是先分配对象,然后直接指向内存地址,最后在初始化对象,
那么就有可能造成实例不同的情况
volatile关键字是防止JVM优化被禁止重排序,解决了重新排序的问题
什么是java的原子操作:
“原子操作(atomic operation)是不需要synchronized(同步)”,所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch;来自于网络
为什么要使用双重校验锁?
当同步的情况下线程A和线程B同时到3时,因为是同步所以线程A先进去创建对象,
线程B在外面等待,当线程A结束后,线程B在进入,当线程B走到4时判断instance == null
此时实例已经创建,则不在重新创建实例
当再有新的线程创建的时候,此时实例已经创建完成,
走到2时,则直接返回当前实例,不进行线程同步
双重校验锁参考代码:
public static InstanceMode06 getInstance() {
/*1*/
if (instance == null) {
/*2*/
synchronized (InstanceMode06.class) {
/*3*/
if (instance == null) {
/*4*/
instance = new InstanceMode06(); /*5*/
}
}
}
return instance;
}
对比HashCode():
效果图(1.6)
:
推荐使用☺
静态内部类
public class InstanceMode07 {
/**
* 私有化构造器 外部不能 new
*/
private InstanceMode07(){
}
private static InstanceMode07 instance;
//提供一个共有方法 使用到时才去创建intance
public static synchronized InstanceMode07 getInstance(){
return SingletonInstance.INSTANCE;
}
//静态内部类
public static class SingletonInstance{
private final static InstanceMode07 INSTANCE = new InstanceMode07();
}
}
优点:
- 在类装载时不会直接创建,保证了懒加载
类的静态属性只会在类加载时初始化
因为使用内部类,在类的初始化时,线程无法进入,保证了线程的安全(JVM机制)
对比HashCode:
效果图(1.7)
:
推荐使用
枚举
public enum InstanceMode08 {
INSTANCE;
public int showEnum(){
return 10;
}
}
借助jdk1.5实现的单例模式,不仅能避免多线程问题,还能防止反序列化,重新创建对象等问题
对比HashCode:
效果图(1.8)
:
<<Effective java>>作者josh Bloch 提倡方式
注:
我是用的Android项目,有没学过的Android的同学直接下载代码即可,下载项目你是跑步起来的哦~
最近文章:
现在是2021/6日,近期我会尽量吧23种设计模式都更新出来,敬请期待吧~
原创不易,您的点赞就是对我最大的支持,点个赞支持一下哦~