当前位置: 首页 > article >正文

【设计模式】创建型-单例模式

文章目录

  • 一、单例模式
  • 二、单例模式的八种实现方式
    • 2.1、饿汉式(静态常量)
    • 2.2、饿汉式(静态代码块)
    • 2.3、懒汉式(线程不安全)
    • 2.4、懒汉式(线程安全,同步方法)
    • 2.5、双重检查
    • 2.6、静态内部类
    • 2.7、枚举


一、单例模式

单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式是一种对象创建型模式。

单例模式有三个要点:

  1. 某个类只能有一个实例
  2. 它必须自行创建这个实例
  3. 它必须自行向整个系统提供这个实例。

二、单例模式的八种实现方式

2.1、饿汉式(静态常量)

/*饿汉式(静态常量)*/
public class Singleton1 {
    //创建一个私有构造器,不让其他类new
    private Singleton1(){}
    //创建一个静态常量
    public static final Singleton1 INSTANCE = new Singleton1();
    //实例方法,方法是静态是为了通过类名调用
    public static Singleton1 newInstance(){
        return INSTANCE;
    }

    public static void main(String[] args) {
        Singleton1 s1 = Singleton1.newInstance();
        Singleton1 s2 = Singleton1.newInstance();
        //比较两个实例是否相等 结果:true
        System.out.println(s1==s2);
    }
}

优缺点:

  • 优点:简单,类加载的时候就完成了实例化,避免了线程安全问题。
  • 缺点:如果没用到这个实例,也会实例化,浪费了内存。

2.2、饿汉式(静态代码块)

/*饿汉式(静态代码块)*/
public class Singleton2 {
    //创建一个私有构造器,不让其他类new
    private Singleton2(){}
    //定义一个静态实例
    public static Singleton2 instance;
    //静态代码块中实例化对象
    static {
         instance= new Singleton2();
    }
    //提供一个公有静态方法,放回实例化对象
    public static Singleton2 newInstance(){
        return instance;
    }

    public static void main(String[] args) {
        Singleton2 s1 = Singleton2.newInstance();
        Singleton2 s2 = Singleton2.newInstance();
        //比较两个实例是否相等 结果:true
        System.out.println(s1==s2);
    }
}

优缺点跟上面的静态常量一样

2.3、懒汉式(线程不安全)

/*
 * 懒汉式
 * 实例是在使用的时候创建,但线程不安全,会创建多个对象
 * */
public class Singleton3 {
    //定义instance静态变量
    private static Singleton3 instance;
    private Singleton3(){}
    //初始化方法,实现懒加载,需要时才创建对象
    public static Singleton3 newInstance() throws InterruptedException {
        //没有实例,则创建对象
        if (instance == null){
            //让线程睡一下,创造多线程进入条件
            Thread.sleep(20);
            instance = new Singleton3();
        }
        //实例化过,直接返回
        return instance;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            //创建多线程,实现Runnable接口,重写run方法
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        //通过哈希码,看对象是否一样
                        System.out.println(Singleton3.newInstance().hashCode());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
}

优缺点:

  • 优点:起到了懒加载效果,需要时才创建对象,但只适合在单线程下使用。
  • 缺点:在多线程情况下,一个线程 进入了if (instance == null)判断语句块,还未来得及往下执行,另一个线程又进来了,这时就产生了多个实例,造成线程不安全。

2.4、懒汉式(线程安全,同步方法)

/*
 * 懒汉式(线程安全,加入同步方法)
 * */
public class Singleton4 {
    //定义instance静态变量
    private static Singleton4 instance;
    private Singleton4(){}
	//加入同步方法,保证只有一个线程进入
    public static synchronized Singleton4 newInstance() throws InterruptedException {
        //没有实例,则创建对象
        if (instance == null){
            //让线程睡一下,创造多线程进入条件
            Thread.sleep(20);
            instance = new Singleton4();
        }
        //实例化过,直接返回
        return instance;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            //创建多线程,实现Runnable接口,重写run方法
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        //通过哈希码,看对象是否一样
                        System.out.println(Singleton4.newInstance().hashCode());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
}

方式一是一个实例,同时锁住了空判断和创建实例,线程安全。但是这就相当于全部锁住了,就跟同步方法的效果一样,线程安全但效率很低

方式二不是一个实例,线程不安全,原因是一个线程进入了空判断,还没往下执行,另一个线程来了,其中一个线程拿到锁,往下执行创建了实例,执行完释放锁后,另一个线程也往下执行了并创建对象,两者创建的对象并不一致。

2.5、双重检查

public class Singleton6 {
    private static Singleton6 instance;
    private Singleton6(){};
    public static Singleton6 newInstance() throws InterruptedException {
        //双重检查,是单例
        if (instance == null){
            //首先判断实例是否为空,空就上锁
            synchronized (Singleton6.class){
                //上锁后,如果上面new出了个对象,此时在这判断是否为空,不为空就直接返回了,确保了只有一个实例
                if (instance == null){
                    Thread.sleep(20);
                    instance = new Singleton6();
                }
            }
        }
        return instance;
    }
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                try {
                    System.out.println(Singleton6.newInstance().hashCode());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

双重检查实际上就是在懒汉式(同步代码块)的内部再添加了一个判断,这样就保证线程安全

在这里插入图片描述

2.6、静态内部类

public class Singleton7 {
    private Singleton7() {}
    //静态内部类里实例化对象,在Singleton7加载的时候,SingletonInstance内部类不加载,只在实例的时候加载
    private static class SingletonInstance{
    //静态属性,实例化对象
        private static final Singleton7 INSTANCE = new Singleton7();
    }
    //提供一个静态的公有方法,返回SingletonInstance类的实例
    public static Singleton7 newInstance(){
        return SingletonInstance.INSTANCE;
    }
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                System.out.println(Singleton7.newInstance().hashCode());
            }).start();
        }
    }
}

这种方式采用了类加载的机制来保证初始化实例时只有一个线程,线程安全。静态内部类在 Singleton7 类被加载时并不会立即实例化,而是在调用 newInstance方法的时候才会实例化静态内部类,通过SingletonInstance类调用实例,从而完成 Singleton 的实例化。类的静态属性只会在第一次加载类的时候初始化,JVM 帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。

2.7、枚举

package com.s.singleton;
/**
 * 枚举
 */
public enum  Singleton8 {
    INSTANCE;
    public static void main(String[] args) {
        for (int i = 0; i < 100 ; i++) {
            new Thread(()->
                    System.out.println(Singleton8.INSTANCE.hashCode())).start();
        }
    }
}

枚举实现是单例的,线程安全,不仅可以解决线程同步,还可以防止反序列化。《Effective Java》作者 Josh Bloch 提倡的方式。


http://www.kler.cn/a/7553.html

相关文章:

  • 数据仓库在大数据处理中的作用
  • 阿里云ACK容器如何配置pod分散在集群的不同节点上
  • 【Framework系列】UnityEditor调用外部程序详解
  • [GXYCTF2019]BabyUpload--详细解析
  • 【网络云计算】2024第46周周考-磁盘管理的基础知识-RAID篇
  • 游戏引擎学习第九天
  • Android上传aar到本地仓
  • MongoDB 插入文档
  • 考勤、充电,绑身份,你的人员定位系统就缺它了!
  • 大数据 | 实验一:大数据系统基本实验 | 熟悉常用的HDFS操作
  • scroll-view不能滚动问题
  • RK3588平台开发系列讲解(同步与互斥篇)信号量介绍
  • Javscript字符串的常用方法有哪些?
  • Python 进阶指南(编程轻松进阶):七、编程术语
  • Python 进阶指南(编程轻松进阶):二、环境配置和命令行
  • 【QT】在公司为了统一代码的风格,那如何在Qt Creater中进行设置
  • ROS实践08 订阅乌龟位姿C++
  • 人人都是数据分析师-数据分析之数据图表可视化(下)
  • 每日一问-ChapGPT-20230406-中医基础-脉诊
  • chatGPT教程
  • 清理MySQL 慢sql日志的方法 flush log/table 注意事项
  • 【ES实战】ES集群节点迁移与缩容补充说明
  • Onedrive for Business迁移方案 | 分享一
  • SQL是什么?它能做什么?SQL的基本书写规则
  • Java面向对象多态
  • 定语从句的省略