Java线程安全问题及其三大线程同步“锁”方案

news2024/11/18 21:46:49

文章目录

    • 一、 线程安全问题概述
    • 二、线程安全问题的demo演示
    • 三、线程同步方案
    • 四、线程同步代码块
    • 五、同步方法
    • 六、Lock锁
    • 七、附录—多线程常用方法

在实际开发过程中,使用线程时最重要的一个问题非线程安全问题莫属。这篇博客会带你由浅入深的初步了解该问题!

一、 线程安全问题概述

线程安全问题指的是,多个线程同时操作同一个共享资源的时候,可能会出现业务安全问题。

下面通过一个取钱的案例演示一下。案例需求如下:

场景:杨过和小龙女是一对夫妻,他们有一个共享账户,余额是10万元,杨过和小龙女同时来取钱,并且2人各自都在取钱10万元,可能出现什么问题呢?

杨过和小龙女假设都是一个线程,本来每个线程都应该执行完三步操作,才算是完成的取钱的操作。但是真实执行过程可能是下面这样子的

​ ① 杨过线程只执行了判断余额是否足够(条件为true),然后CPU的执行权就被小龙女线程抢走了。

​ ② 小龙女线程也执行了判断了余额是否足够(条件也是true), 然后CPU执行权又被杨过线程抢走了。

​ ③ 杨过线程由于刚才已经判断余额是否足够了,直接执行第2步,吐出了10万元钱,此时共享账户月为0。然后CPU执行权又被小龙女线程抢走。

​ ④ 小龙女线程由于刚刚也已经判断余额是否足够了,直接执行第2步,吐出了10万元钱,此时共享账户月为-10万。

你会发现,在这个取钱案例中,两个人把共享账户的钱都取了10万,但问题是只有10万块钱啊!!!

以上取钱案例中的问题,就是线程安全问题的一种体现。

二、线程安全问题的demo演示

先定义一个共享的账户类

public class Account {
    private String cardId; // 卡号
    private double money; // 余额。

    public Account() {
    }

    public Account(String cardId, double money) {
        this.cardId = cardId;
        this.money = money;
    }

    // 杨过 小龙女同时过来的
    public void drawMoney(double money) {
        // 先搞清楚是谁来取钱?
        String name = Thread.currentThread().getName();
        // 1、判断余额是否足够
        if(this.money >= money){
            System.out.println(name + "来取钱" + money + "成功!");
            this.money -= money;
            System.out.println(name + "来取钱后,余额剩余:" + this.money);
        }else {
            System.out.println(name + "来取钱:余额不足~");
        }
    }

    public String getCardId() {
        return cardId;
    }

    public void setCardId(String cardId) {
        this.cardId = cardId;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }
}

在定义一个是取钱的线程类

public class DrawThread extends Thread{
    private Account acc;
    public DrawThread(Account acc, String name){
        super(name);
        this.acc = acc;
    }
    @Override
    public void run() {
        // 取钱
        acc.drawMoney(100000);
    }
}

最后,再写一个测试类,在测试类中创建两个线程对象

public class ThreadTest {
    public static void main(String[] args) {
         // 1、创建一个账户对象,代表两个人的共享账户。
        Account acc = new Account("ICBC-110", 100000);
        // 2、创建两个线程,分别代表杨过 小龙女,再去同一个账户对象中取钱10万。
        new DrawThread(acc, "杨过").start(); // 小明
        new DrawThread(acc, "小龙女").start(); // 小红
    }
}

运行程序,执行效果如下。你会发现两个人都取了10万块钱,余额为-10完了。
在这里插入图片描述

三、线程同步方案

为了解决前面的线程安全问题,我们可以使用线程同步思想。同步最常见的方案就是加锁,意思是每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动释放锁,然后其他线程才能再加锁进来。

采用加锁的方案,就可以解决前面两个线程都取10万块钱的问题。怎么加锁呢?Java提供了三种方案

  • 1.同步代码块
  • 2.同步方法
  • 3.Lock锁

四、线程同步代码块

同步代码块它的作用就是把访问共享数据的代码锁起来,以此保证线程安全。

//锁对象:必须是一个唯一的对象(同一个地址)
synchronized(锁对象){
    //...访问共享数据的代码...
}

使用同步代码块,来解决前面代码里面的线程安全问题。我们只需要修改DrawThread类中的代码即可。

// 杨过 小龙女线程同时过来的
public void drawMoney(double money) {
    // 先搞清楚是谁来取钱?
    String name = Thread.currentThread().getName();
    // 1、判断余额是否足够
    // this正好代表共享资源!
    synchronized (this) {
        if(this.money >= money){
            System.out.println(name + "来取钱" + money + "成功!");
            this.money -= money;
            System.out.println(name + "来取钱后,余额剩余:" + this.money);
        }else {
            System.out.println(name + "来取钱:余额不足~");
        }
    }
}

此时再运行测试类,观察是否会出现不合理的情况。
在这里插入图片描述
最后关于锁对象如何选择的问题提醒如下:

1.建议把共享资源作为锁对象, 不要将随便无关的对象当做锁对象
2.对于实例方法,建议使用this作为锁对象
3.对于静态方法,建议把类的字节码(类名.class)当做锁对象

五、同步方法

其实同步方法,就是把整个方法给锁住,一个线程调用这个方法,另一个线程调用的时候就执行不了,只有等上一个线程调用结束,下一个线程调用才能继续执行。

// 同步方法
public synchronized void drawMoney(double money) {
    // 先搞清楚是谁来取钱?
    String name = Thread.currentThread().getName();
    // 1、判断余额是否足够
    if(this.money >= money){
        System.out.println(name + "来取钱" + money + "成功!");
        this.money -= money;
        System.out.println(name + "来取钱后,余额剩余:" + this.money);
    }else {
        System.out.println(name + "来取钱:余额不足~");
    }
}

同步方法也是有锁对象,只不过这个锁对象没有显示的写出来而已。
1.对于实例方法,锁对象其实是this(也就是方法的调用者)
2.对于静态方法,锁对象时类的字节码对象(类名.class)

最终,总结一下同步代码块和同步方法有什么区别?

1.不存在哪个好与不好,只是一个锁住的范围大,一个范围小
2.同步方法是将方法中所有的代码锁住
3.同步代码块是将方法中的部分代码锁住

六、Lock锁

Lock锁是JDK5版本专门提供的一种锁对象,通过这个锁对象的方法来达到加锁,和释放锁的目的,使用起来更加灵活。格式如下

1.首先在成员变量位子,需要创建一个Lock接口的实现类对象(这个对象就是锁对象)
	private final Lock lk = new ReentrantLock();
2.在需要上锁的地方加入下面的代码
	 lk.lock(); // 加锁
	 //...中间是被锁住的代码...
	 lk.unlock(); // 解锁

使用Lock锁改写前面DrawThread中取钱的方法,代码如下

// 创建了一个锁对象
private final Lock lk = new ReentrantLock();

public void drawMoney(double money) {
        // 先搞清楚是谁来取钱?
        String name = Thread.currentThread().getName();
        try {
            lk.lock(); // 加锁
            // 1、判断余额是否足够
            if(this.money >= money){
                System.out.println(name + "来取钱" + money + "成功!");
                this.money -= money;
                System.out.println(name + "来取钱后,余额剩余:" + this.money);
            }else {
                System.out.println(name + "来取钱:余额不足~");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lk.unlock(); // 解锁
        }
    }
}

七、附录—多线程常用方法

在这里插入图片描述在这里插入图片描述

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

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

相关文章

FolkMQ 内存型消息中间件,v1.0.18 发布

简介 采用 “内存运行” “快照持久化” “Broker 集群模式”(可选)基于 Socket.D 网络应用协议 开发,使用“多路复用”技术。全新设计,自主架构! 角色功能生产端发布消息(Qos0、Qos1)、发布…

SOLIDWORKS参数化工具如何设置部分提取

编制参数表是参数化设置必不可少的一环,提取零部件参数又是生成参数表所必须的步骤,然而很多时候,模型的量级很大,需要变化的零部件只有三分之一,那如果全部提取出来,将耗费大量的时间,因此部分…

Swagger页面报错Resolver error at definitions

问题描述 打开swagger页面报错Resolver error at definitions 原因分析: 从错误提示可以看出,是由map引起的原因,具体是因为swagger配置没有默认添加map的复杂结构引起的,需要手动添加。 解决方案: 找到swagger配置类…

黄金代理商怎么做才会受客户青睐?

黄金代理商是市场中推广现货黄金投资的角色,黄金代理商做的越好,推广的效果越明显,投资者就越多,这样他们就可以获得多一点黄金代理平台的佣金。所以,黄金代理商们会使劲浑身解数,让自己获得更多客户的青睐…

【ARM Trace32(劳特巴赫) 使用介绍 13 -- Trace32 Var 变量篇】

文章目录 Trace32 查看变量值Var.view 查看变量值Var.view 查看数据类型的大小Var.view 根据变量地址查看变量值 地址类型判断 Trace32 查看变量值 步骤1 步骤2 步骤3: 步骤4: 查看结构体变量 str_t32 的值 struct t32_str {uint32_t t32_val…

【JavaEE进阶】 Spring使用注解存储对象

文章目录 🌴序言🍀前置⼯作:配置扫描路径🎄添加注解存储 Bean 对象🌳类注解🚩为什么要这么多类注解🚩注解之间的联系 🎋⽅法注解 Bean🚩⽅法注解需要配合类注解使⽤ ⭕总…

合合信息旗下启信宝与鹏城实验室达成数据托管合作,“AI靶场”让数据管理更精准

数字经济时代,数据已成为新型生产要素。通过“数据托管”等形式对数据进行集中管理,有助于保护数据主体权益,促进数据共享和运用效率,对数字经济的发展具有重要意义。近期,在深圳数据交易所(简称“深数所”…

如何使用eXtplorer+cpolar内网穿透搭建个人云存储实现公网访问

文章目录 1. 前言2. eXtplorer网站搭建2.1 eXtplorer下载和安装2.2 eXtplorer网页测试2.3 cpolar的安装和注册 3.本地网页发布3.1.Cpolar云端设置3.2.Cpolar本地设置 4.公网访问测试5.结语 1. 前言 通过互联网传输文件,是互联网最重要的应用之一,无论是…

Cloak斗篷技术不知道?超实用干货,FP商家必读!

都2023年了,还有很多人犹豫要不要入局独立站。心动不如行动,如果想要在跨境独立站中干出一番新事业,要赶紧动起来。部分卖家在出海初期,都想着出售FP产品,涉及到FP产品,各大主流平台上都有严格的审核机制&a…

如何教你四个类搞定分层自动化测试框架

写在前面 我们刚开始做自动化测试,可能写的代码都是基于原生写的代码,看起来特别不美观,而且感觉特别生硬。 来看下面一段代码: 如果你想学习自动化测试,我这边给你推荐一套视频,这个视频可以说是B站播放…

华三AC+AP部署无线基本配置

拓扑图: 1、创建管理VLAN与AP管理VLAN、终端接入VLAN、配置管理VLAN IP地址 设备管理VLAN 56、AP管理VLAN 101、终端接入VLAN 10 AC、HeXin、JieRu。(创建VLAN)[H3C]vlan 101[H3C-vlan101]description AP-vlan[H3C]vlan 56[H3C-vlan56]desc…

SAP UI5 walkthrough step9 Component Configuration

在之前的章节中,我们已经介绍完了MVC的架构和实现,现在我们来讲一下,SAPUI5的结构 这一步,我们将所有的UI资产从index.html里面独立封装在一个组件里面 这样组件就变得独立,可复用了。这样,无所什么时候我…

【51单片机系列】74HC595实现对LED点阵的控制

本文是关于LED点阵的使用,使用74HC595模块实现对LED点阵的控制。 文章目录 一、8x8LED点阵的原理1.1 LED点阵显示原理1.2 LED点阵内部结构图1.3 开发板上的LED点阵原理图1.4 74HC595芯片 二、使用74HC595模块实现流水灯效果三、 使用74HC595模块控制LED点阵对角线亮…

IDEA中表明或者字段找不到时报红

问题 idea 中mysql的sql语句报红,无论表名还是表字段 原因 是由于sql方言导致的 当我们选择某一个sql方言的时候,xml配置会按照指定规则校验sql是否规范,并给出提示 解决方案 取消sql方言,设置sql方言为None。设置完重启idea既…

Mysql启动占用内存过高解决

Hi, I’m Shendi Mysql启动占用内存过高解决 前言 最近服务器内存不够用了,甚至还出现了内存溢出问题,导致程序宕机。但请求与用户量并没有多少,所以从各种启动的程序中想方设法的尽可能的减少其占用的内存。 而在我的服务器中,…

二叉树的非递归遍历(详解)

二叉树非递归遍历原理 使用先序遍历的方式完成该二叉树的非递归遍历 通过添加现有项目的方式将原来编写好的栈文件导入项目中 目前项目存在三个文件一个头文件,两个cpp文件: 项目头文件的代码截图:QueueStorage.h 项目头文件的代码&#xff…

golang学习笔记——TCP端口扫描器

文章目录 TCP 端口扫描器非并发版本并发版本goroutine 池并发版 TCP 端口扫描器 time.Sincefunc Since net包Conn 接口func Dialfunc DialTimeoutfunc FileConn TCP 端口扫描器 非并发版本 package mainimport ("fmt""net" )func main() {for i : 21; i …

HarmonyOS学习 第1节 DevEco Studio配置

俗话说的好,工欲善其事,必先利其器。我们先下载官方的开发工具DevEco Studio. 下载完成后,进行安装。 双击DevEco Studio,点击Next按照指引完成安装 重新启动DevEco,点击 Agree 进入环境配置,安装Node.js和ohpm 点击Ne…

网络编程基础api

1. IP 协议 1.1 IP 分片 (1)IP 分片和重组主要依靠 IP 头部三个字段:数据报标识、标志和片偏移 以太网帧的 MTU 是 1500 字节; 一个每个分片都有自己的 IP 头部,它们都具有相同的标识值,有不同的片偏移…

智能优化算法应用:基于孔雀算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用:基于孔雀算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用:基于孔雀算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.孔雀算法4.实验参数设定5.算法结果6.参考文献7.MATLAB…