版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_40646143/article/details/85013248
单例模式
核心作用 : 保证一个类只有一个实例 , 并且提供一个访问该实习的全局访问点 .
常见的应用场景 :
- Windows 的 Tsask Manager (任务管理器) 就是典型的单例模式 当你打开任务管理器的时候,只要你能不关闭任务管理器页面,你电脑只能显示一个任务管理器页面 。
- 项目中 , 读取配置文件的类 , 一般也只有一个对象。 没有必要每次使用配置文件数据,每次 new 一个对象去读取。
- 数据库连接池的设计一般也是采用单例模式 , 因为数据库连接是一种数据库资源。
- 在 Spring 中 , 每个 Bean 默认就是单例的 , 这样做的优点是 Spring 容器可以管理 。
- 在 Spring MVC 框架爱/struts1 框架中 , 控制器对象也是单例的 。
单例模式有什么优点呢?
- 由于单例模式只生成一个实例,减少了系统性能开销 , 当一个对象的产生需要比较多的资源时 , 如 读取配置 , 产生其他依赖对象时 , 则可以通过在应用启动时直接产生一个单例对象, 然后永久驻留内存的放式解决 。
- 单例模式可以在系统设置全局的访问点 , 优化环共享资源访问 , 例如 可以设计一个单例类, 负责所有数据表的映射处理 。
常见单例模式的四种实现放式
1), 饿汉式单例模式
/**
* 饿汉式单例模式
*
* 当类初始化的时候,进行创建当前类的实例 线程安全 没有延迟加载
*/
public class SingletionDemo {
//1 声明一个私有的静态变量
private static SingletionDemo instance=new SingletionDemo();
//2 私有化构造器 避免外部直接创建对象
private SingletionDemo(){}
//3 创建一个对外的公共的静态方法 访问该变量
//方法没有同步 效率高
public static SingletionDemo getInstance() {
return instance;
}
}
饿汉式单例模式总结如下:
- 在代码上, static 变量会在类装载时初始化 , 此时也不会涉及多个线程访问该对象的问题 。虚拟机保证只会装载一次该类 , 肯定不会发生并发访问的问题 。 因此, 可以省略Synchronized 关键字 。
- 也存在这样一个问题: 如果只是加载本类 , 而不是调用getInstance() , 甚至永远没有调用 ,则会造成资源浪费 !
2),懒汉式单例模式
/** 懒汉式 单例模式
* @author 晓电脑
*
* 单线程状态下是只是一个实例对象 如果多个线程访问则会出现问题
* 多线程则会是多个对象 需要加上synchronized
*/
public class SingletionDemo01 {
//1 声明一个私有的静态变量
private static SingletionDemo01 instance;
//2 私有构造器 避免外部直接创建对象
private SingletionDemo01(){}
//3 创建一个对外的公共的静态方法 访问该变量 如果变量没有对象 创建对象
public static SingletionDemo01 getInstance() throws InterruptedException {
if (null == instance){
instance=new SingletionDemo01();
}
return instance;
}
}
如果我们使用main方法进行测试如下
public static void main (String[] args) throws InterruptedException {
System.out.println("_________单线程状态下_________");
System.out.println(SingletionDemo01.getInstance());
System.out.println(SingletionDemo01.getInstance());
}
main是一个单线程, 所以 上面打印的俩个地址是一样的 , 如果模拟多线程的情况下,则会出现问题 ,我们改动懒汉单例模式如下
/** 懒汉式 单例模式
* @author 晓电脑
*
* 单线程状态下是只是一个实例对象 如果多个线程访问则会出现问题
* 多线程则会是多个对象 需要加上synchronized
*/
public class SingletionDemo01 {
//1 声明一个私有的静态变量
private static SingletionDemo01 instance;
//2 私有构造器 避免外部直接创建对象
private SingletionDemo01(){}
//3 创建一个对外的公共的静态方法 访问该变量 如果变量没有对象 创建对象
//需要在static 后面加上synchronized
public static SingletionDemo01 getInstance(Long time) throws InterruptedException {
if (null == instance){
// 加入延迟时间 time
Thread.sleep(time);
instance=new SingletionDemo01();
}
return instance;
}
}
接下来创建一个线程,对懒汉单例模式进行访问
/**
* 创建一个线程 来执行懒汉单例模式
*/
static class ThreadDemo extends Thread{
private Long time;
//构造器对延迟时间进行初始化
public ThreadDemo(Long time){
this.time=time;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + "---->" + SingletionDemo01.getInstance(time));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
main方法进行测试多线程情况下访问懒汉单例模式
public static void main (String[] args) throws ClassNotFoundException, InterruptedException {
System.out.println("_________单线程状态下_________");
// System.out.println(SingletionDemo01.getInstance());
// System.out.println(SingletionDemo01.getInstance());
System.out.println("______模拟多线程_______");
//设置延迟时间
ThreadDemo demo = new ThreadDemo(100L);
//设置线程名称
demo.setName("zs");
//启动线程
demo.start();
ThreadDemo demo1 = new ThreadDemo(300L);
demo1.setName("www");
demo1.start();
}
运行结果如下
总结 :
- 懒汉单例模式,需要加上 Synchronized 同步锁 ,才能再多线程的情况下也是同一个对象,否则只是单线程下是同一个对象而多线程情况下,是不同的对象 。
- Lazy load ! 延迟加载, 懒加载 ! 真正用的时候才加载 !
- 也有一个问题: 资源利用率高了 。 但是 , 每次调用getInstance()方法都要同步,并发效率较低 。
3),静态内部类实现放式(也是一种懒加载放式)
/**静态内部类实现方式
* @author 晓电脑
*/
public class StaticSingletion {
//1 私有化构造器 避免外部类直接创建对象
private StaticSingletion(){
}
//2 创建static 内部类
private static class DemoStatic{
private static final StaticSingletion instance=new StaticSingletion();
}
//3 提供外部访问的入口
public static StaticSingletion getInstance(){
return DemoStatic.instance;
}
}
总结 :
- 外部类没有 static 属性 ,则不会像饿汉式那样立即加载对象 。
- 只有真正调用了 getInstance() 方法,才会加载静态内部类 。加载类时是线程安全的 。 instance 是static final 类型 ,保证了内存中只有这样的一个实例存在 ,而且 只能被赋值一次 ,从而保证了线程安全性 。
- 兼备了并发高效调用和延迟加载的优势 。
4),使用枚举的方式实现单例模式
package com.tuogo.instance;
/**
* 使用枚举来实现单例
*/
public enum EnumSingletion {
INSTANCE;
//1 创建私有属性
private EnumSingletionDemo02 instance;
//2 私有构造器 类加载的时候给这个属性赋值
private EnumSingletion(){
instance=new EnumSingletionDemo02();
}
//3 提供外界访问的入口
public EnumSingletionDemo02 getInstance() {
return instance;
}
}
/**要实现单例的类
*/
class EnumSingletionDemo02{
}
main测试类代码 如下
public static void main (String[] args) {
EnumSingletionDemo02 instance1 = EnumSingletion.INSTANCE.getInstance();
EnumSingletionDemo02 instance2 = EnumSingletion.INSTANCE.getInstance();
EnumSingletionDemo02 instance3= EnumSingletion.INSTANCE.getInstance();
System.out.println(instance1);
System.out.println(instance2);
System.out.println(instance3);
}
总结:
- 实现简单
- 枚举本身就是单例模式。 由JVM从根本上提供保障! 避免了通过反射和反序列化的漏洞!
- 缺点: 无延迟加载
5),双重检测锁实现 (不推荐使用)
- 因为 由于编译器优化原因和JVM底层内部模型原因偶尔会出现问题 , 不建议使用 。
- 这里就不做具体的展现
那我们如何选用适合哪一种单例模式呢?
- 单例对象 占用 资源少 , 不需要 延迟加载 : 枚举类 好与 饿汉式
- 单例对象 占用 资源大 , 需要 延迟加载 : 静态内部类式单利模式 好与 懒汉式
拓展
IDEA怎么生成UML类图?(也就是对于已有的代码,想将相关类绘制成UML类图)
第一步
File -> Setting->Tools -> JAVA class Diagrams
第二步
第三步生成类图
在你想生成类图的类右键 -> Diagrams -> Show Diagram...
如图
这时你可以左键类名不丢,也可以把类拖进来