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

 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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ComfyUI如何中文汉化

comfyui中文地址如下&#xff1a; https://github.com/AIGODLIKE/AIGODLIKE-ComfyUI-Translationhttps://github.com/AIGODLIKE/AIGODLIKE-ComfyUI-Translation如何安装&#xff1f; 1. git安装 进入项目目录下的custom_nodes目录下&#xff0c;然后进入控制台&#xff0c;运…

ppt美化的的几个小技巧

最简单的提升方式&#xff1a;加一层相对透明的矩形底色。 1、图标设置为透明的 方法&#xff1a;双击图片-》格式-》颜色-》设置透明色 2、使用渐变填充透明度配合作为文本底色。 1&#xff09;如透明度为90%&#xff0c;颜色会变浅&#xff0c;然后作为文本的底色。 2&…

iPad绘画之旅:从小白到文创手账设计的萌系简笔画探索

&#x1f482; 个人网站:【 海拥】【神级代码资源网站】【办公神器】&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交流的小伙伴&#xff0c;请点击【全栈技术交流群】 iPad的出现&#xff0c;不仅改变了我们对电子设…

HarmonyOS共享包HAR

共享包概述 OpenHarmony提供了两种共享包&#xff0c;HAR&#xff08;Harmony Archive&#xff09;静态共享包&#xff0c;和HSP&#xff08;Harmony Shared Package&#xff09;动态共享包。 HAR与HSP都是为了实现代码和资源的共享&#xff0c;都可以包含代码、C库、资源和配…

【Java基础】Java中异常分类,他们之间的区别?

&#x1f341;Java中异常分哪两类 &#x1f341;Java中异常类&#x1f341;受检异常&#x1f341;非受检异常 &#x1f341;拓展知识仓&#x1f341;什么是Throwable&#x1f341;Error和Exception的区别和联系&#x1f341; 列举几个常用的RuntimeException&#x1f341;Java异…

索引是如何提高查询性能的?

引言问&#xff1a;如何提高一条查询SQL的性能&#xff1f;答&#xff1a;最常用的方式就是加「索引」。问&#xff1a;索引为什么就能提高查询性能&#xff1f;答&#xff1a;索引就像一本书的目录&#xff0c;用目录查当然很快。问&#xff1a;为什么通过目录就能提高查询速度…

restTemplate支持https忽略证书

由于公司的某个服务器由http转为https&#xff0c;导致原先的接口不可用&#xff0c;不过网上一堆忽略ssl都可以用就不多写了&#xff0c;写几个奇葩的问题 首先报错都是 sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.cert…

前端开发之通过vue-office组件实现文件预览

前端开发之通过vue-office组件实现文件预览 前言效果图docx文件xlsx文件pdf文件 vue中简单案例1、安装组件2、vue中代码 前言 在实现文件预览的时候我们可以通过vue-office组件来实现文件的预览效果 效果图 docx文件 xlsx文件 pdf文件 vue中简单案例 1、安装组件 整体安装…

Intel® SGX Instruction References(五)

文章目录 前言一、Intel SGX Instruction Syntax and Operation1.1 ENCLS Register Usage Summary1.2 ENCLU Register Usage Summary1.3 ENCLV Register Usage Summary1.4 Information and Error Codes1.5 Internal CREGs1.6 Concurrent Operation Restrictions 二、Intel SGX …

腾讯云4核8G服务器三年优惠价格表

腾讯云轻量服务器4核8G12M有三年优惠价吗&#xff1f;有&#xff0c;但是不怎么优势&#xff0c;相对于云轻量2核2G4M带宽三年价格是540元、2核4G5M带宽3年优惠价756元&#xff0c;4核8G12M轻量应用服务器三年价格是5292元&#xff0c;怎么样&#xff1f;还想买吗&#xff1f;阿…

个性化定制的知识付费小程序,为用户提供个性化的知识服务,知识付费saas租户平台

明理信息科技知识付费saas租户平台 在当今数字化时代&#xff0c;知识付费已经成为一种趋势&#xff0c;越来越多的人愿意为有价值的知识付费。然而&#xff0c;公共知识付费平台虽然内容丰富&#xff0c;但难以满足个人或企业个性化的需求和品牌打造。同时&#xff0c;开发和…

FCIS 2023网络安全创新大会-核心PPT资料下载

一、峰会简介 本次会议的主题是“AI大模型、人工智能与智能制造安全、攻击面管理与供应链安全”。 1、AI大模型 会议首先探讨了AI大模型在网络安全领域的应用。AI大模型是一种基于深度学习的模型&#xff0c;具有强大的特征提取和分类能力&#xff0c;可以用于检测和防御各种…

一开始我还不信!高德导航红绿灯竟然能读秒?

高德导航红绿灯为啥能读秒&#xff1f; 1 内部员工吐露 每天工作其实就是负责自己片区的红绿灯&#xff0c;一大早就去校对时间&#xff0c;然后发布到后台。是的&#xff0c;统计出来的&#xff0c;而且还是人工统计&#xff0c;有误差请见谅[害羞] 真的是很辛苦了&#xf…

低代码平台:明年IT规划的关键

背景 在这个快速变化的世界中&#xff0c;企业面临前所未有的挑战。明年的经济形势预计将遭遇巨大变化&#xff0c;全球市场也正在经历深刻调整。在这样的环境下&#xff0c;企业的应用系统也面临着重新评估和调整的需求。为了保持竞争力&#xff0c;企业必须确保其IT系统不仅…

WAVE SUMMIT迎来第十届,文心一言将有最新披露!

10句话2分钟&#xff0c;挑战成功说服宿管阿姨开门&#xff0c;这个人群中的“显眼包”是一个接入文心大模型4.0游戏里的NPC&#xff0c;妥妥 “工具人”实锤&#xff5e; 尝试用AI一键自动识别好坏咖啡豆&#xff0c;看一眼便知好坏&#xff0c;真正“颜值即正义”&#xff0c…

手写Promise完整介绍

✨ 专栏介绍 在现代Web开发中&#xff0c;JavaScript已经成为了不可或缺的一部分。它不仅可以为网页增加交互性和动态性&#xff0c;还可以在后端开发中使用Node.js构建高效的服务器端应用程序。作为一种灵活且易学的脚本语言&#xff0c;JavaScript具有广泛的应用场景&#x…

一文看懂所有字符编码标准

文章目录 ASCII128个符号不够用欧洲国家亚洲国家 GB2312GBKGB18030UnicodeUTF-8 QA最高位b7是什么意思?UTF-8和UTF-16的区别UTF-8和UTF-32的区别 ASCII ASCII&#xff08;American Standard Code for Information Interchange&#xff09;是一种字符编码标准&#xff0c;最初…

基于业务功能级别的流量控制

之前产品线上发生过若干次因为tomcat连接池被耗尽而导致宕机的故障&#xff0c;而具体根源原因则各不尽相同。有因为调用和被调用的服务申请相同的分布式锁而导致死锁的&#xff0c;有因为发送内部或外部的JMS消息发生堵塞的&#xff0c;有因为某个存在性能问题的接口被较多调用…

如何使用队列处理 API 速率限制

对于遇到速率限制的应用程序来说也是一个挑战&#xff0c;因为它需要“放慢速度”或暂停。这是一个典型的场景&#xff1a; 初始请求&#xff1a;当应用程序发起与 API 的通信时&#xff0c;它会请求特定的数据或功能。API 响应&#xff1a; API 处理请求并响应请求的信息或执…

linxu重启网络服务失败——Failed to start LSB: Bring up/down networking.

一、出现问题的场景 在虚拟机中的Linux系统启动后&#xff0c;发现没有网络&#xff0c;执行ifconfig 发现自己配置的ens33网卡没有启动。 接着&#xff0c;执行systemctl restart network 重启网络服务失败 查看网络状态&#xff0c;执行 systemctl status network 发现报错…