java线程安全问题分析、3种解决办法

2023-10-28

目录

一 线程不安全

1.1 代码

1.2 解析

解析--卖出不存在的票

解析--卖出相同的票

1.3 解决办法

二 解决方法1:同步代码块

2.1 代码

 2.2 解析

三 解决方法2:同步方法

3.1 代码

 3.2 说明

*四 解决方法3:Lock锁

代码


一 线程不安全

1个窗口卖100张票:单线程不会出现线程安全
3个窗口卖100张票:各卖各的不同票(多线程程序,没有访问共享数据,不会产生问题)
3个窗口卖100张票:可以卖相同的一张票(多线程访问了共享的数据,会产生安全问题)

1.1 代码


3个窗口卖100张票:可以卖相同的一张票(多线程访问了共享的数据,会产生安全问题)

 线程不安全的代码

package threadSafe;

/**
 * 实现卖票安全
 */
public class MyThreadSafe implements Runnable{
    private int ticketAccount = 100;
    @Override
    public void run() {
        while(ticketAccount>0){
            //为了提高线程出现安全的问题,睡眠一下,提高概率
            try {
                Thread.sleep(200);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"正在卖第"+(100-ticketAccount+1)+"张票");
            ticketAccount--;
        }


    }
}
package threadSafe;


public class MainMethod {
    public static void main(String[] args) {

        //创建1个实现类,实现共享票源
        MyThreadSafe myThreadSafe =  new MyThreadSafe();
        //创建3个线程模拟3个窗口卖票
        Thread t0 = new Thread(myThreadSafe);
        Thread t1 = new Thread(myThreadSafe);
        Thread t2 = new Thread(myThreadSafe);
        t0.start();
        t1.start();
        t2.start();
    }
}

执行结果

卖出不存在的的票

卖出相同的一张票

1.2 解析


解析--卖出不存在的票


ticketAccount=1 剩最后一张票的时候:

t0线程抢到了CPU的执行权,进入循环体,sleep了
t2线程抢到了CPU的执行权,进入循环体,sleep了
t1线程抢到了CPU的执行权,进入循环体,sleep了

t2醒了抢到cpu的执行权,打印正在卖第100张票,执行ticketAccount--
ticketAccount=0,0不大于0,t2就停止循环了

t1醒了抢到cpu的执行权,在循环体里继续执行,此时ticketAccount=0,打印正在卖第101张票,执行ticketAccount--,
ticketAccount=-1,-1不大于0,t1就停止循环了

t0醒了抢到cpu的执行权,在循环体里继续执行,此时ticketAccount=-1,打印正在卖第102张票,执行ticketAccount--,
ticketAccount=-2,-2不大于0,t0就停止循环了

解析--卖出相同的票


t1 t2 同时执行到了正在卖第44张票,这时候还没执行ticketAccount--

1.3 解决办法


上述的线程安全问题不能使之出现,
我们可以让一个线程在访问共享数据的时候,(不管它有没有失去cpu的执行权),让其他线程只能等待,
等待当前线程执行完了,再允许其他线程继续执行.
保证始终一个线程在卖票就OK了

java引入了线程同步机制,有3种方法完成线程同步操作:
1 同步代码块
2 同步方法
3 锁机制

 

二 解决方法1:同步代码块

格式:
synchronized (锁对象){
  可能会出现线程安全的代码(访问共享数据的代码)
}

2.1 代码

package threadSafe;

/**
 * 实现卖票安全
 */
public class MyThreadSafe implements Runnable {
    private int ticketAccount = 100;

    //创建一个锁对象
    Object object = new Object();

    @Override
    public void run() {
        //同步代码块
        synchronized (object) {
            while (ticketAccount > 0) {
                //为了提高线程出现安全的问题,睡眠一下,提高概率
                try {
                    Thread.sleep(200);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在卖第" + (100 - ticketAccount + 1) + "张票");
                ticketAccount--;

            }
        }


    }
}

备注:main方法的代码同之前不变

 结果

 2.2 解析

同步技术的原理:

使用了一个锁对象,叫同步锁(也叫对象锁、对象监视器)
锁对象的作用:把同步代码块锁住,只让一个线程在同步代码块中执行

t0抢到了cpu的执行权,执行run()方法,遇到了同步码块,这时,t0会检查同步代码块是否有锁对象,
发现有,就会获取到锁对象,进入到同步中执行.

t1抢到了cpu的执行权,执行run()方法,遇到了同步码块,这时,t1会检查同步代码块是否有锁对象,
发现没有,t1就会进入到阻塞状态,会一直等待t0线程归还锁对象.
一直等到t0线程执行完同步中的代码,会把锁对象归还给同步代码块,t1才能获取到锁对象,进入到同步中执行.

总结:
同步中的线程,没有执行完毕,不会释放锁
同步外的线程,没有锁,进不去同步

但是会出现一个问题:程序频繁地判断锁,获取锁,释放锁,程序的效率会降低.

三 解决方法2:同步方法

格式:

public synchronized 返回值 method(){
    可能会产生线程安全的代码
}

3.1 代码

package threadSafe2;

/**
 * 实现卖票安全
 */
public class MyThreadSafe implements Runnable {
    private int ticketAccount = 50;

    @Override
    public void run() {
        //同步方法
        saleTicket();

    }

    //同步方法
    public synchronized void saleTicket() {
        while (ticketAccount > 0) {
            //为了提高线程出现安全的问题,睡眠一下,提高概率
            try {
               Thread.sleep(200);
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "正在卖第" + (50 - ticketAccount + 1) + "张票");
            ticketAccount--;

        }
    }

}

备注:main方法的代码同之前不变

结果

 3.2 说明

 同步方法也会把方法内部的代码锁住,只让一个线程执行.
同步方法的锁对象是谁?是new的实现类对象:new MyThreadSafe(),就是this
静态同步方法的锁对象是谁?是本类的class文件对象 (不能是this,this是创建对象之后产生的,静态方法优先于对象)

*四 解决方法3:Lock锁

提供了比 synchronized 更广泛的锁定操作

Lock接口中的方法:
    lock()
    unlock()

使用步骤:
1 在成员位置创建一个ReentrantLock对象
2 在可能会出现安全问题的代码,调用Lock接口中的方法lock():加同步锁
3 在可能会出现安全问题的代码,调用Lock接口中的方法unlock():释放锁

代码

package threadSafe3;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 实现卖票安全
 */
public class MyThreadSafe implements Runnable {
    private int ticketAccount = 80;

    //1 在成员位置创建一个ReentrantLock对象
    Lock lock = new ReentrantLock();
    @Override
    public void run() {
        //2 在可能会出现安全问题的代码前,调用Lock接口中的方法lock:加同步锁
        lock.lock();
        while (ticketAccount > 0) {
            //为了提高线程出现安全的问题,睡眠一下,提高概率
            try {
                Thread.sleep(200);

            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "正在卖第" + (80 - ticketAccount + 1) + "张票");
            ticketAccount--;
        }
        //3 在可能会出现安全问题的代码后,调用Lock接口中的方法unlock():释放锁
        lock.unlock();

    }



}

备注:main方法的代码同之前不变

结果

关于线程的状态和线程的通信请点这里

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

java线程安全问题分析、3种解决办法 的相关文章

  • Java 使用服务器证书对 jar 进行签名

    是否可以使用服务器证书来签署 java web start 应用程序 我想知道的是它是否有效 我的服务器有一个受信任的证书 并且我想重复使用同一证书来签署应用程序 现在 我有这样的警告 此 jar 包含其签名者证书的 ExtendedKey
  • 使用 Nginx 时缺少 HTTP 状态代码名称

    我正在使用 Nginx 将所有 HTTP 请求重定向到 HTTPS 在我的 Spring Boot 应用程序中 这是我正在使用的 nginx 配置 通过它我可以将所有请求重定向到 Https 但是当我这样做时 我得到了状态码返回正确 但没有
  • 为什么byteArray的长度是22而不是20?

    我们尝试从字符串转换为Byte 使用以下 Java 代码 String source 0123456789 byte byteArray source getBytes UTF 16 我们得到一个长度为 22 字节的字节数组 我们不确定这个
  • 使用 google-api-java-client 的 2 足 OAuth

    有谁知道如何将 2 legged OAuth 与 google api java client 一起使用 我正在尝试访问 Google Apps 配置 API 以获取特定域的用户列表 以下不起作用 HttpTransport transpo
  • Java 相当于 Perl 的 s/// 运算符?

    我有一些代码正在从 Perl 转换为 Java 它大量使用了正则表达式 包括s 操作员 我已经使用 Perl 很长时间了 但仍然习惯 Java 的做事方式 特别是 字符串似乎更难使用 有谁知道或有一个完全实现的Java函数s 这样它就可以处
  • 使用 Hibernate 和 Apache DBCP 的 MySQL 连接池问题

    看来我的应用程序有问题 当应用程序在启动后闲置很长时间 我不确定确切的时间 时 我会在日志中收到以下错误消息 我使用 Spring Hibernate MySQL 和 ApacheDBCP 进行连接池 ERROR org hibernate
  • Java 中支持多少维数组,例如 a[1][1][1][1]....[1]? [复制]

    这个问题在这里已经有答案了 Java支持多少维数组a 1 1 1 1 1 我可以为数组声明无限数量的维度吗 数组维数限制为 255 有趣的是 JLS定义的Java编程语言没有这样的限制 但是你可以在JVM规范 http docs oracl
  • @Cachable 在没有输入参数的方法上?

    我有问题 org springframework cache annotation Cachable注解 Bean public ConcurrentMapCache cache return new ConcurrentMapCache
  • IntSummaryStatistics的summaryStatistics方法

    为什么空 IntStream 上的 summaryStatistics 方法返回整数的最大和最小值作为流中存在的最大和最小 int 值 IntStream intStream IntStream of IntSummaryStatistic
  • 处理 ANTLR 4 中的错误

    遵循后接受的答案 https stackoverflow com a 18137301 2279200的指示处理 ANTLR4 中的错误 https stackoverflow com q 18132078 2279200问题 我遇到了以下
  • 在 Java Swing 元素中使用 HTML 样式是不好的做法吗?

    使用 HTML 设置 Swing 元素的样式被认为是不好的做法吗 举个例子 如果我想让标签变大并变红一次 我有两个选择 使用 API 调用 JLabel label new JLabel This is a title label setF
  • 读取不失真的灰度 PNG 图像文件

    我需要读取和处理大量的灰度 PNG 文件 我的意思是 如果它们在 Photoshop 或 GIMP 中打开 则图像模式为灰度 而不是具有灰度值的 RGB 图像 ImageIO 似乎没有实现这一点 它似乎将所有图像文件视为 sRGB 这会破坏
  • logcat 信息出现在 Android Studio 的“运行”选项卡中

    我的 android studio 运行选项卡很简单 然后它变得更难并给我更多信息 例如 logcat 中的信息 如何禁用或删除第二张图片中出现的更多信息并返回到第一张图片中的第一个外观 我只需要正在运行的 flutter 应用程序的日志输
  • 如何从 java.sql.Blob 类型的 zip 文件中读取和提取 zip 条目,而无需将 FileInputStream 或文件路径作为字符串 java

    public static void unzipFiles java sql Blob zip throws Exception String paths byte blobAsBytes zip getBytes 1 int zip le
  • 使用 InputStream 通过 TCP 套接字接收多个图像

    每次我从相机捕获图像时 我试图将多个图像自动从我的 Android 手机一张一张地发送到服务器 PC 问题是read 函数仅在第一次时阻塞 因此 从技术上讲 只有一张图像被接收并完美显示 但在那之后当is read 回报 1 该功能不阻塞
  • Eclipse 在单独的窗口中打开代码

    我正在 eclipse 中编程 在两个显示器设置上运行 在其中一台显示器上 我只获得了项目资源管理器和编辑器作为自定义透视图 而在另一台显示器上 我获得了其他工具 例如控制台 调试 任务 变量 断点等 例如 当我单击任务视图中的任务时 这将
  • 找出该月第一个星期日/星期一等的日期

    我想在java中检测每个月第一周 第二周的第一个星期日 星期一的日期 我怎样才能实现它 我已经检查了 java 中的 Calendar 类和 Date 类 但无法找到解决方案 所以请帮助我解决这个问题 Calendar calendar C
  • 如何从 jenkins 的现有项目生成 .hpi 插件

    我正在尝试使用 jenkins 的性能插件 但最新版本存在一些问题 如链接中所述 https issues jenkins ci org browse JENKINS 27100 https issues jenkins ci org br
  • 混合语言源目录布局

    我们正在运行一个使用多种不同语言的大型项目 Java Python PHP SQL 和 Perl 到目前为止 人们一直在自己的私有存储库中工作 但现在我们希望将整个项目合并到一个存储库中 现在的问题是 目录结构应该是什么样的 我们应该为每种
  • 在没有 ODBC 的情况下从 Java 操作 Access 数据库

    我想从我的 Java 项目操作 Microsoft Access 数据库 accdb 或 mdb 文件 我不想使用 Microsoft 的 JDBC ODBC Bridge 和 Access ODBC 驱动程序 因为 JDBC ODBC 桥

随机推荐

  • python进阶:线程、进程和协程

    前言 线程 进程和协程都是实现多任务 同一时间可以做多个事情就叫做多任务 的方法 并发 一段时间内做不同的事情 比如一个人吃饭喝水 吃的时候不能喝 喝的时候不能吃 只有在一段时间 吃了再喝或者喝了在吃 并行 同一时间做不同的事情 比如边听音
  • 谈乱序执行和内存屏障

    10多年前的程序员对处理器乱序执行和内存屏障应该是很熟悉的 但随着计算机技术突飞猛进的发展 我们离底层原理越来越远 这并不是一件坏事 但在有些情况下了解一些底层原理有助于我们更好的工作 比如现代高级语言多提供了多线程并发技术 如果不深入下来
  • [Linux]Kali安装Deepin-wine-WeChat

    Kali版本 2022 2 KDE环境 实体机 wget O https deepin wine i m dev setup sh sh apt get install com qq weixin deepin 其他的QQ TIM登录后会崩
  • python PyAV库解析H264视频流及保存指定帧

    PyAV是一个视频处理库 可用于读取 写入和处理视频 要使用PyAV读取视频关键帧 首先需要安装PyAV 可以使用以下命令安装PyAV pip install av 安装完成后 您可以使用以下代码打开视频文件并读取关键帧 import av
  • python数据可视化第八章使用matplotlib绘制高级图表

    使用matplotlib绘制高级图表 1 绘制高等图 等高线图是地形图上高程相等的相邻各点所连成的j闭合曲线 它会将地面上海拔高度相同的点连成环线 之后将环线垂直投影到某一水平面上 并按照 定的比例缩绘到图纸上 常见于山谷 山峰或梯度下降算
  • CloudOS:物联网开发平台,云上开发,边端交付

    什么是物联网 物联网 Internet of Things 简称IoT 是指通过各种信息传感器 射频识别技术 全球定位系统 红外感应器 激光扫描器等各种装置与技术 实时采集任何需要监控 连接 互动的物体或过程 采集其声 光 热 电 力学 化
  • iOS开发中,使用PC查看/修改沙盒文件

    简介 在进行iOS开发中 APP尤其是游戏 在沙盒中可以存放游戏资源包或者数据 例如Json文件 AssetBundle包 在使用服务器下载资源包 存放到本地沙盒中 可以大大减少APP本身的大小 当然 在调试的时候 如果频繁的更换服务器的A
  • 处理告警“ warning #69-D integer conversion resulted in truncation”的方法

    今天分享一个常见的语法告警 就是实参与形参的类型参数对于不上 错误之处 首先我们先看一下告警提示 告警 c 49 warning 69 D integer conversion resulted in truncation 其翻译过来意思就
  • 二 动态SQL和多对一,一对多

    二 动态SQL和多对一 一对多 10 多对一处理 测试环境搭建 按照查询嵌套处理 子查询 按照结果嵌套处理 联表查询 11 一对多 按照结果嵌套处理 按照查询嵌套处理 小结 12 动态 SQL IF choose when otherwis
  • xpath下载安装——Python爬虫xpath插件下载安装(2023.8亲测可用!!)

    目录 1 免费下载插件链接 若失效评论区留言发送最新链接 2023 7亲测可用 2 安装插件 1 打开chrome浏览器页面 点击 右上角三个点 gt 扩展程序 gt 管理拓展程序 若没找到点更多工具 2 打开右上角开发者模式 3 将刚刚下
  • 关于maven创建java-web工程加载过慢如何解决问题

    如何解决maven创建java web工程加载过慢如何解决问题 在创建项目时在这个界面中 添加设置一组变量archetypeCatalog internal 然后点击完成继续创建就可以了 但是这样做每次创建项目都要重复添加 很麻烦 如果大家
  • Spring实现封装自定义注解@Trimmed清除字符串前后的空格

    在Spring中实现字符串清除的方法有很多 原生方法String自带trim 方法 或者使用StringUtils提供的trim 方法 通常可以将上面的方式封装成自定义注解的形式去实现来节省更多的业务代码 Trimmed java impo
  • Foggy_driving数据集下载以及将其转换成VOC数据格式

    Foggy driving数据集官方下载地址 百度网盘下载链接 https pan baidu com s 1q4dhnlX doxlt13Mi uFZQ 提取码 2ap3 VOC格式的Foggy driving数据集百度网盘下载链接 ht
  • html基础题目

    1 在html中 样式表按照应用方式可以分为三种类型 其中不包括 d A 内嵌样式表 B 行内样式表 C 外部样式表文件 D 类样式表 2 在HTML中 可以使用 d 标记向网页中插入GIF动画文件 A
  • 从零开始实现自己的Kalimba——Cocos Creator新手教程系列(一)使用瓦片图Tiledmap设计游戏地图

    瓦片图Tiledmap可能是很多2d游戏开发者的偏爱 本节就Cocos Creator如何使用瓦片图进行详细的讲解 Tiled地图编辑器的下载安装不再赘述 下面介绍如何使用地图编辑器 创建新地图 地图方向选正常 其中45度和等角 交错 适合
  • uview2.0封装http请求实战以及常见请求传参实录

    1 前言 2 使用步骤 2 1 配置请求拦截器以及api集中管理配置 2 2 main js中进行引入请求拦截器 2 3 页面中引入请求方法并使用 1 前言 uview2 0是uniapp开发中使用频率相对来讲比较高的一款框架 今天从实战角
  • python爬虫十二:初步使用Scrapy框架爬取数据

    1 通过pipelines操作拿到的数据 要点一 爬虫文件必须通过关键字yield生成器才能与pipelines建立链接才可以操作 当运行爬虫文件之后执行的顺序如下图简介 如果重写模块或者内部方法可能会造成麻烦 往下翻阅可以看到open s
  • UC测试实习生笔试面试

    笔试 2014年5月14号 中大公教C402 一个半钟 好紧的时间 除了选择题和编程题 其他都感觉不好啊 一 选择题 1 chmod 755 filename 之后 文件权限为 A rwxr xr x 2 403状态表示 3 用于转义的字符
  • Python3网络爬虫--爬取百度搜索结果(附源码)

    文章目录 一 准备工作 1 工具 二 思路 1 爬虫思路 2 数据抽取思路 三 源代码 四 结果 五 总结 今天更新一篇基础 使用Python爬取百度搜索结果 最后将爬取结果保存到txt文本文件中 一 准备工作 1 工具 1 Google
  • java线程安全问题分析、3种解决办法

    目录 一 线程不安全 1 1 代码 1 2 解析 解析 卖出不存在的票 解析 卖出相同的票 1 3 解决办法 二 解决方法1 同步代码块 2 1 代码 2 2 解析 三 解决方法2 同步方法 3 1 代码 3 2 说明 四 解决方法3 Lo