Java多线程技术五——单例模式与多线程

news2024/9/27 23:30:55

1 概述

        本章的知识点非常重要。在单例模式与多线程技术相结合的过程中,我们能发现很多以前从未考虑过的问题。这些不良的程序设计如果应用在商业项目中将会带来非常大的麻烦。本章的案例也充分说明,线程与某些技术相结合中,我们要考虑的事情会更多。在学习本章的过程中,我们只需要考虑一件事情,那就是:如果使单例模式与多线程结合时是安全、正确的。

2 单例模式与多线程

        在标准的23个设计模式中,单例模式在应用中是比较常见的。但多数常规的该模式教学资料并没有结合多线程技术进行介绍,这就造成在使用结合多线程的单例模式时会出现一些意外。

3 立即加载/饿汉模式

        立即加载指的是,使用类的时候已经将对象创建完毕。常见的实现办法就是new实例化,也被称为“饿汉模式”。

public class MyObject {
    //立即加载方法 == 饿汉模式
    private static MyObject object = new MyObject();
    private MyObject(){

    }
    public static MyObject getInstance(){
        return object;
    }

}
public class MyThread extends Thread{
    @Override
    public void run(){
        System.out.println(MyObject.getInstance().hashCode());
    }
}
public class Run1 {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        t1.start();
        t2.start();
        t3.start();
    }
}

66f314561cdc42398fc9b5d22a3d957d.png

        控制台打印的hashcode是同一个值,说明对象是一个,也就实现了立即加载型单例模式。此代码为立即加载模式,缺点是不能有其他实例变量,因为getInstance()方法没有同步,所以有可能出现非线程安全问题。

4 延迟加载/懒汉模式

        延迟加载就是调用get()方法时,实例才被创建。常见的实现办法就是在get()方法中进行new实例化,也被称为“懒汉模式”。

4.1 延迟加载解析

        先看下面一段代码。

public class MyObject {
    private static MyObject object;

    public MyObject() {
    }
    public static MyObject getInstance(){
        if(object == null){
            object = new MyObject();
        }
        return object;
    }
}
public class MyThread extends Thread{
    @Override
    public void  run(){
        System.out.println(MyObject.getInstance().hashCode());
    }
}
public class Run1 {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        t1.start();
        t2.start();
        t3.start();
    }
}

ff9b69c3702340c9b27709e4fa80bd70.png

 4.2 延迟加载的缺点

                前面两个实验虽然使用“立即加载”和“延迟加载”实现了单例模式,但在多线程环境中,“延迟加载”示例中的代码完全是错误的,根本不能保持单例的状态。下面来看如何在多线程环境中结合错误的单例模式创建出多个实例的。 

public class MyObject {
    private static MyObject object;

    public MyObject() {
    }
    public static MyObject getInstance(){
        try {
            if(object == null){
                //模拟在创建对象之前做一些准备工作
                Thread.sleep(3000);
                object = new MyObject();
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        return object;
    }
}
public class MyThread extends Thread{
    @Override
    public void run(){
        System.out.println(MyObject.getInstance().hashCode());
    }
}
public class Run1 {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        t1.start();
        t2.start();
        t3.start();
    }
}

f197c607d54c47b6a385151a86f3ed4c.png

        控制台打印3个不同的hashCode,说明创建了3个对象,并不是单例的。这就是“错误的单例模式”,如何解决呢?

4.3 延迟加载的解决方案 

        (1)声明synchronzied关键字

        既然多个线程可以同时进入getInstance()方法,只需要对getInstance()方法声明synchronzied关键字即可。修改MyObject.java类。

public class MyObject {
    private static MyObject object;

    public MyObject() {
    }
    synchronized public static MyObject getInstance(){
        try {
            if(object == null){
                //模拟在创建对象之前做一些准备工作
                Thread.sleep(3000);
                object = new MyObject();
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        return object;
    }
}

326e93f5dd0049f0a4c616c06910e615.png

        此方法在加入同步synchronzied关键字后得到相同实例的对象,但运行效率很低。下一个线程想要取得 对象,必须等待上一个线程释放完锁之后,才可以执行。那换成同步代码块可以解决吗?

(2)尝试同步代码块

        修改MyObject.java类。

public class MyObject {
    private static MyObject object;

    public MyObject() {
    }
    public static MyObject getInstance(){
        try {
            synchronized (MyObject.class){
                if(object == null){
                    //模拟在创建对象之前做一些准备工作
                    Thread.sleep(3000);
                    object = new MyObject();
                }
            }

        }catch (InterruptedException e){
            e.printStackTrace();
        }
        return object;
    }
}

         此方法加入同步synchronzied语句块后得到相同实例对象,但运行效率也非常低,和synchronzied同步方法一样是同步运行的。下面继续更改代码,尝试解决这个问题。

(3)针对某个重要的代码进行单独的同步。

修改MyObject.java类。

public class MyObject {
    private static MyObject object;

    public MyObject() {
    }
    public static MyObject getInstance(){
        try {

                if(object == null){
                    //模拟在创建对象之前做一些准备工作
                    Thread.sleep(3000);
                    synchronized (MyObject.class) {
                        object = new MyObject();
                    }
                }


        }catch (InterruptedException e){
            e.printStackTrace();
        }
        return object;
    }
}

d18820343cba46039770f296235eadcc.png

        此方法使同步synchronzied语句块只对实例化对象的关键代码进行同步。从语句的结构上讲,运行效率却是得到了提升,但遇到多线程的情况还是无法得到同一个实例对象。

(4)使用DCL双检查锁机制

public class MyObject {
    private  volatile static MyObject object;

    public MyObject() {
    }
    public static MyObject getInstance(){
        try {
            if(object == null){
                Thread.sleep(2000);
                synchronized (MyObject.class){
                    if(object == null){
                        object = new MyObject();
                    }
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        return object;
    }
}

 使用volatile修改变量object,使该变量在多个线程间可见,另外禁止 object = new MyObject()代码重排序。object = new MyObject()包含3个步骤:

        1、memory = allocate();//分配对象的内存空间

        2、ctorInstance(memory);//初始化对象

        3、object = memory;//设置instance指向刚分配的内存地址

JIT编译器有可能将这三个步骤重新排序。

        1、memory = allocate();//分配对象的内存空间

        2、object = memory;//设置instance指向刚分配的内存地址

        3、ctorInstance(memory);//初始化对象

这时,构造方法虽然还没有执行,但object对象已具有内存地址,即值不是null。当访问object对象中的值时,是当前声明数据类型的默认值。

创建线程类MyThread.java代码如下。

public class MyThread extends Thread{
    @Override
    public void run(){
        System.out.println(MyObject.getInstance().hashCode());
    }
}

     

public class Run1 {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        t1.start();
        t2.start();
        t3.start();
    }
}

e5c10ed8b5de48eaa67856539d801ca8.png  

        可见,使用DCL双检查锁成功解决了懒汉模式下的多线程问题。DCL也是大多数多线程结合单例模式使用的解决方案。

5 使用静态内置类实现单例模式

        DCL可以解决多线程单例模式的非线程安全问题。还可以使用其他办法达到同样的效果。

public class MyObject {
    private static class MyObjectHandler{
        private static MyObject object = new MyObject();
    }

    public MyObject() {
    }
    public static MyObject getInstance(){
        return MyObjectHandler.object;
    }
}

 

public class MyThread extends Thread{
    @Override
    public void run(){
        System.out.println(MyObject.getInstance().hashCode());
    }
}
public class Run1 {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        t1.start();
        t2.start();
        t3.start();
    }
}

c90168a09bc040ebb2ed202c4c4cad01.png

6 使用static代码块实现单例模式

        静态代码块中的代码在使用类的时候就已经执行,所以可以使用静态代码块的这个特性实现单例模式。

public class MyObject {
    private static MyObject object = null;

    public MyObject() {
    }
    static {
        object = new MyObject();
    }
    public static MyObject getInstance(){
        return  object;
    }
}

 

public class MyThread extends Thread{
    @Override
    public void run(){
        for (int i = 0; i < 5; i++) {
            System.out.println(MyObject.getInstance().hashCode());
        }
    }
}
public class Run1 {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        t1.start();
        t2.start();
        t3.start();
    }
}

6197e85b5484485eb782a1342aabf123.png

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1334205.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

公司电脑文件透明加密保护,防泄密系统——防止核心文件、文档、设计图纸、源代码、音视频等数据资料外泄!

天锐绿盾透明加密防泄密系统是一种强大的数据保护方案&#xff0c;它采用先进的透明加密技术&#xff0c;能够在不影响员工工作习惯的前提下&#xff0c;对公司的所有电子文件进行自动、即时的加密处理。这种系统对于保护企业的核心数据资产至关重要。 PC访问地址&#xff1a;w…

plsql连接报ORA-12537

客户新电脑装上了plsql&#xff0c;连接数据库时报如上错误&#xff0c;但是别的电脑都可以正常连接&#xff0c;先检查了下TNS配置&#xff0c;发现没问题&#xff0c;数据库连接数也足够&#xff0c;百思不得其解 后面去数据库服务器上查看了监听日志文件&#xff0c;连接报错…

elasticsearch-py 8.x的一些优势

​ 早在 2022 年 2 月,当 Elasticsearch 8.0 发布时,Python 客户端也发布了 8.0 版本。它是对 7.x 客户端的部分重写,并带有许多不错的功能(如下所述),但也带有弃用警告和重大更改。今天,客户端的 7.17 版本仍然相对流行,每月下载量超过 100 万次,占 8.x 下载量的 ~50…

JVM初识-----01章

一.虚拟机与java虚拟机的区别以及共同点 1.虚拟机&#xff08;Virtual Machine&#xff0c;简称VM&#xff09; 是一种能够在物理计算机上模拟一台完整的计算机系统的软件。它运行在宿主操作系统之上&#xff0c;可以提供一个独立的运行环境&#xff0c;使得在不同的操作系统上…

事实验证文章分类 Papers Category For Fact Checking

事实验证文章分类 Papers Category For Fact Checking By 2023.11 个人根据自己的观点&#xff0c;花了很多时间整理的一些关于事实验证领域证据召回&#xff0c;验证推理过程的文献综合整理分类&#xff08;不是很严谨&#xff09;。 引用请注明出处 欢迎从事事实验证Fact…

「Vue3面试系列」Vue3.0里为什么要用 Proxy API 替代 defineProperty API ?

文章目录 一、Object.defineProperty为什么能实现响应式 小结 二、proxy三、总结参考文献 一、Object.defineProperty 定义&#xff1a;Object.defineProperty() 方法会直接在一个对象上定义一个新属性&#xff0c;或者修改一个对象的现有属性&#xff0c;并返回此对象 为什么…

单例模式(C++实现)

RAII运用 只能在栈上创建对象 只能在堆上创建的对象 单例模式 设计模式 懒汉模式 解决线程安全 优化 饿汉模式 饿汉和懒汉的区别 线程安全与STL与其他锁

共模电容:又一款EMC滤波神器?|深圳比创达电子(上)

传统共模滤波器的局限性 通常我们讨论EMC问题中的噪声及干扰&#xff0c;多是共模噪声、共模干扰&#xff1b;所以常见的滤波、防护器件&#xff0c;多是共模形式&#xff0c;典型的代表就是共模电感&#xff1b;共模电感因其对共模干扰呈高阻特性、而对差模信号几无损耗&…

iOS技术博客:App备案指南

&#x1f4dd; 摘要 本文介绍了移动应用程序&#xff08;App&#xff09;备案的重要性和流程。备案是规范App开发和运营的必要手段&#xff0c;有助于保护用户权益、维护网络安全和社会秩序。为了帮助开发者更好地了解备案流程&#xff0c;本文提供了一份最新、最全、最详的备…

振弦采集仪在地质灾害监测中的作用与意义

振弦采集仪在地质灾害监测中的作用与意义 振弦采集仪是一种地质灾害监测仪器&#xff0c;用于测量地面的震动和振动。它可以记录地质灾害发生时地震波在地面上的传播情况&#xff0c;通过分析数据来评估地质灾害的严重程度和影响范围。振弦采集仪在地质灾害监测中发挥着重要的…

洛谷——【数据结构1-2】二叉树

文章目录 题目【深基16.例1】淘汰赛题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1基本思路&#xff1a;代码 【深基16.例3】二叉树深度题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1基本思路&#xff1a;代码 [USACO3.4] 美国血统 American Heritage题目描…

【阿里云盘替身“小白羊”,释放急速,做回自己】解除阿里云盘限速,开启云上人生

小白羊网盘 软件下载地址&#xff1a;https://github.com/gaozhangmin/aliyunpan/releases 界面略丑&#xff0c;但不限速 下载速度对比 阿里云盘 小白羊 近乎十倍的差距 近期阿里云盘更新了自动同步功能&#xff0c;能自动同步多个文件夹&#xff0c;多电脑工作者的福音&am…

基于Java SSM框架实现二手交易平台网站系统项目【项目源码+论文说明】

基于java的SSM框架实现二手交易平台网站系统演示 摘要 21世纪的今天&#xff0c;随着社会的不断发展与进步&#xff0c;人们对于信息科学化的认识&#xff0c;已由低层次向高层次发展&#xff0c;由原来的感性认识向理性认识提高&#xff0c;管理工作的重要性已逐渐被人们所认…

公众号推荐流量玩法的3个秘密

从微信生态的流量触点来看&#xff0c;公众号链接着私聊、朋友圈、微信群、小程序、视频号、搜一搜、看一看等一切与目标用户能接触到的中转站 流量的尽头是私域。而对于大部分普通人来说&#xff0c;公众号可以作为私域的第一站。且相比个人微信号&#xff0c;其有着深度价值…

抖店开通后只有零星的一些散单,是哪里出了问题?新手做店教程!

我是王路飞。 如果你的抖店开通后&#xff0c;只有零星的一些散单的话&#xff0c;大概率是选品和出单玩法上出现了问题。 要知道&#xff0c;我们只是在抖音开店卖货&#xff0c;所以我们所有的工作重心都应该围绕【店铺产品】展开的&#xff0c;而不要把时间和精力浪费在账…

分库分表之后,id 主键如何处理?

问&#xff1a;分库分表之后&#xff0c;id 主键如何处理&#xff1f; 其实这是分库分表之后你必然要面对的一个问题&#xff0c;就是 id 咋生成&#xff1f;因为要是分成多个表之后&#xff0c;每个表都是从 1 开始累加&#xff0c;那肯定不对啊&#xff0c;需要一个全局唯一的…

武汉市东湖高新区管委会副主任李世庭一行调研中科驭数

近日&#xff0c;武汉市光谷东湖高新区管委会党工委委员、副主任李世庭一行莅临中科驭数&#xff0c;调研考察中科驭数DPU芯片研发和产业化进展&#xff0c;东湖高新区投促局、光谷金控相关负责人一同莅临调研。 中科驭数武汉研发中心是驭数DPU研发团队的重要力量之一。自2022…

Unity2017升级到Unity2018在Window7上输出空异常错误问题

Unity2017升级到Unity2018在Window7上输出空异常错误问题 一、环境Window7二、现象Unity报空异常&#xff08;.NET 4.x Equivalent&#xff09;三、日志四、解决方案第一种解决方案第二种解决方案 一、环境Window7 二、现象Unity报空异常&#xff08;.NET 4.x Equivalent&…

Isaac Sim urdf文件导入

本教程展示如何在 Omniverse Isaac Sim 中导入 urdf 一. 使用内置插件导入urdf 安装urdf 插件 方法是转到“window”->“Extensions” 搜索框中输入urdf, 并启用 通过转至Isaac Utils -> Workflows -> URDF Importer菜单来访问 urdf 扩展。 表格中的 1,2,3 对应着…

单聊和群聊

TCP协议单聊服务端&#xff1a; import java.awt.BorderLayout; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.Vec…