单利模式介绍
简介
说白了, 单利模式, 从字面上就能理解, 就是采取一定的方法保证在整个系统中, 对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)
举例
比如Hibernate的SessionFactory,它充当数据存储源的代理, 并负责创建Session对象, SessionFactory并不是轻量级的, 一般情况下, 一个项目通常只需要一个SessionFactory就够了,这时就会使用到单利模式
Hibernate可能现在基本用的不多了,但是Spring大家应该都用, 在Spring中, 一般声明的Bean, 如果没有特殊配置, 那么它就是单利的
单利模式实现的八种方式
- 饿汉式(静态常量)
- 饿汉式(静态代码块)
- 懒汉式(线程不安全)
- 懒汉式(线程安全, 同步方法)
- 懒汉式(线程安全, 同步代码块)
- 双重检查
- 静态内部类
- 枚举
饿汉式(静态常量)
步骤
- 私有化构造方法(防止 new)
- 类的内部创建对象
- 向外部暴露一个静态的公共方法, getInstance
代码
package com.dance.design.designmodel.simpleinterestmodel;
public class SimpleOne {
}
/**
* 饿汉式(静态常量)
*/
class SingleOne{
// 创建静态常量
private static final SingleOne singleOne = new SingleOne();
// 私有化构造
private SingleOne(){
}
/**
* 提供一个公共静态方法
* @return 类
*/
public static SingleOne getInstance() {
return singleOne;
}
}
我感觉这没啥好测试的, 我下面就直接写代码了, 但凡做过开发的,应该都看的懂
总结
- 优点
- 写法简单, 线程安全
- 缺点
- 不是懒加载, 如果不用的话, 就会造成内存浪费
- 为什么线程安全?
- 基于类加载实现的, 存放于元空间, 应为是static的 在类加载的时候就会创建
- 为什么不用,会造成内存浪费?
- 应为在类加载的时候就创建, 不是用的时候才创建, 所以不是懒加载, 应为是一上来就加载, 并且是放在元空间的, 并不会被垃圾回收器回收, 如果不用就一直 存在, 所以会造成内存浪费
- 结论
- 如果是一定会用到, 可以用, 但是还是建议使用懒加载的
饿汉式(静态代码块)
步骤
- 私有化构造
- 声明静态成员
- 静态代码块初始化
- 对外提供公共静态方法
代码
package com.dance.design.designmodel.simpleinterestmodel;
public class SimpleTwo {
}
/**
* 饿汉式(静态代码块)
*/
class SingleTwo{
// 创建静态常量
private static final SingleTwo singletwo;
static {
singletwo = new SingleTwo();
}
// 私有化构造
private SingleTwo(){
}
/**
* 提供一个公共静态方法
* @return 类
*/
public static SingleTwo getInstance() {
return singletwo;
}
}
总结
其实和静态常量方式,一样, 就是从直接New改到了代码块中
懒汉式(线程不安全)
步骤
- 私有化构造
- 声明静态成员变量
- 提供对外公共方法
- 在公共方法中去创建对象
代码
package com.dance.design.designmodel.simpleinterestmodel;
public class SimpleThree {
}
/**
* 懒汉式(线程不安全)
*/
class SingleThree{
// 创建静态常量
private static SingleThree singleThree;
// 私有化构造
private SingleThree(){
}
/**
* 提供一个公共静态方法
* @return 类
*/
public static SingleThree getInstance() {
if (null == singleThree) {
singleThree = new SingleThree();
}
return singleThree;
}
}
总结
- 优点
- 提供了懒加载
- 缺点
- 线程不安全
- 为什么说提供了懒加载?
- 应为并没有在类加载的时候就创建, 而是在第一次调用的时候才创建的
- 为什么说线程不安全
- 应为没有锁机制, 导致多个线程可能同时进入到if块的内部, 导致都创建了对象,导致多利的存在, 破坏了单利模式的存在
- 总结
- 不要用, 不为啥
懒汉式(线程安全, 同步方法)
步骤
- 私有化构造
- 声明静态成员
- 提供对外公共静态方法
- 在方法中创建对象
- 在方法上加内置锁
代码
package com.dance.design.designmodel.simpleinterestmodel;
public class SimpleFour {
}
/**
* 懒汉式(线程安全, 同步方法)
*/
class SingleFour{
// 创建静态常量
private static SingleFour singleFour;
// 私有化构造
private SingleFour(){
}
/**
* 提供一个公共静态方法
* @return 类
*/
public synchronized static SingleFour getInstance() {
if (null == singleFour) {
singleFour = new SingleFour();
}
return singleFour;
}
}
总结
- 优点
- 懒加载
- 线程安全
- 缺点
- 效率问题
- 为什么线程安全?
- 应为在方法上加上了内置锁, 并且方法是static的, 所以是类锁, 保证了所有的线程访问这个方法都必须排队, 所以保证了线程安全
- 有什么效率问题?
- 是应为所有的线程排队这个问题, 应为大量线程获取的时候,其实第一个线程就创建好了,其他的线程其实是不需要排队的, 所以存在效率问题
- 总结
- 不要用, 不为啥
懒汉式(线程安全, 同步代码块)
步骤
- 私有化构造
- 声明静态成员
- 对外提供公共静态方法
- 在方法内部创建对象
- 添加同步代码块
代码
package com.dance.design.designmodel.simpleinterestmodel;
public class SimpleFive {
}
/**
* 懒汉式(线程安全, 同步代码块)
*/
class SingleFive {
// 创建静态常量
private static SingleFive singleFive;
// 私有化构造
private SingleFive() {
}
/**
* 提供一个公共静态方法
*
* @return 类
*/
public static SingleFive getInstance() {
if (null == singleFive) {
// 其实还是类锁
synchronized (SingleFive.class) {
singleFive = new SingleFive();
}
}
return singleFive;
}
}
总结
- 我都不是很想总结这个单同步代码块的单利模式了, 在写的时候思考到了一些问题, 和大家聊聊问题吧
- 大家看代码,这个同步代码块,其实还是类锁, 这个同步块如果加在if块的内部, 还是会导致线程不安全说的那个问题,就是多线程卡在if块里面, 如果是加载if块外面, 就会和同步方法一样,直接卡到开头, 所我感觉我起的这个名字很好"单利Five", 怪不得排在第五
- 不要用,不为啥
双重检查
步骤
参考同步方法的步骤
将同步块放在if块的外面,然后在同步块的外面再包一层if块
代码
package com.dance.design.designmodel.simpleinterestmodel;
public class SimpleSix {
}
/**
* 双重检查
*/
class SingleSix {
// 创建静态常量
private static volatile SingleSix singleSix;
// 私有化构造
private SingleSix() {
}
/**
* 提供一个公共静态方法
*
* @return 类
*/
public static SingleSix getInstance() {
// 再包一层if块用于卡线程, 如果有了就可以直接获取, 解决排队问题
if (null == singleSix) {
// 其实还是类锁
synchronized (SingleSix.class) {
if (null == singleSix){
singleSix = new SingleSix();
}
}
}
return singleSix;
}
}
总结
- 优点
- 懒加载, 线程安全, 效率高
- 缺点
- emmm... 不知道怎么写了.
- 为什么线程安全?
- 应为采用了类锁, 线程去排队, 并且成员变量采用了volatile修饰
- 为什么需要volatile修饰?
- 因为这种双重检测机制在JDK1.5之前是有问题的,问题还是出在(//创建实例),由所谓的无序写入造成的。一般来讲,当初始化一个对象的时候,会经历
- 内存分配 -> 初始化 -> 返回对象引用
- 这种方式产生的对象是一个完整的对象,可以正常使用。但是JAVA的无序写入可能会造成顺序的颠倒,即
- 内存分配 -> 返回对象引用 -> 初始化
- 这种情况下对应到(//创建实例)就是singleton已经不是null,而是指向了堆上的一个对象,但是该对象却还没有完成初始化动作。当后续的线程发现singleton不是null而直接使用的时候,就会出现意料之外的问题。
- 解决方案:
- JDK1.5之后,可以使用volatile关键字修饰变量来解决无序写入产生的问题,因为volatile关键字的一个重要作用是禁止指令重排序,即保证不会出现内存分配、返回对象引用、初始化这样的顺序,从而使得双重检测真正发挥作用
- 为什么效率高?
- 应为在类锁的,外部和内部都有检查, 在创建一次之后,以后就不会走类锁了, 所以后续不会排队
- 总结
- 没错, 写不出来缺点, 就用这种吧
静态内部类
步骤
- 私有化构造
- 声明静态成员
- 声明静态内部类
- 内部类声明属性
- 提供对外公共静态方法
代码
package com.dance.design.designmodel.simpleinterestmodel;
public class SimpleSeven {
}
/**
* 双重检查
*/
class SingleSeven {
// 私有化构造
private SingleSeven() {
}
private static final class SingleSevenHolder {
// 创建静态常量
static final SingleSeven singleSeven = new SingleSeven();
}
/**
* 提供一个公共静态方法
*
* @return 类
*/
public static SingleSeven getInstance() {
// 再包一层if块用于卡线程, 如果有了就可以直接获取, 解决排队问题
// 其实还是类锁
return SingleSevenHolder.singleSeven;
}
}
总结
- 优点
- 线程安全, 懒加载, 效率高
- 缺点
- emm.. 一样不知道
- 为什么懒加载, 不是static的吗?
- 应为类只有在第一次调用或者其他类依赖的时候才会进行类加载, 类加载, 这个内部类没有没其他类依赖, 并且是内部的所以在加载外部类的时候,也不会加载内部类, 只有第一次调用 getInstance方法时才会触发类加载
- 为什么是线程安全的?
- 类加载只会触发一次, 除非类卸载,
- 为什么效率高?
- 只触发一次类加载, 不需要判断(双重检查的判断都省略了), 直接可以返回, 不用排队,
- 总结
- 用就完了,不为啥
枚举
步骤
- 创建属性
- 创建方法
代码
package com.dance.design.designmodel.simpleinterestmodel;
public class SimpleEight {
public static void main(String[] args) {
SingleEight.INSTANCE.say();
}
}
/**
* 静态内部类
*/
enum SingleEight {
INSTANCE;
public void say(){
System.out.println("say 单利模式");
}
}
总结
- 优点
- 线程安全, 开发简单, 防止反序列化创建, 效率高
- 缺点
- 一样不清楚
- 总结
- 用就完了, 大佬推荐[Effective Java作者 Josh Bloch 提倡]
源码剖析
JDK源码中的单利模式
- JDK中 java.long.Runtime就是经典的单利模式(饿汉式)
代码
package java.lang;
import java.io.*;
import java.util.StringTokenizer;
import sun.reflect.CallerSensitive;
import sun.reflect.Reflection;
/**
* 第一句话就表明了这是一个单利的类
* Every Java application has a single instance of class
* .......
*/
public class Runtime {
// 声明私有的成员变量 一上来就new 所以是饿汉式(静态变量)
private static Runtime currentRuntime = new Runtime();
// 提供对外的公共静态方法
public static Runtime getRuntime() {
return currentRuntime;
}
// 私有化构造方法
/** Don't let anyone else instantiate this class */
private Runtime() {}
}
单利模式注意事项和细节说明
- 单利保证了系统中只存在一个对象, 节省了系统资源, 对于一些需要频繁创建销毁的对象, 使用单利可以提高系统性能
- 当你想要获得一个单利类的时候,应该是调用公共静态方法获取,而不是通过new
- 使用场景
- 需要频繁的进行创建和销毁的对象
- 创建对象耗时过多或耗费资源过多(即: 重量级对象), 但又经常用的对象
- 工具类
- 频繁访问数据库或文件的对象
- 数据源
- Session工厂