给他个卖票的机会,他能卖出负数票. 多线程安全问题演示

news2024/11/28 10:55:44

文章目录

      • 1.1 线程安全产生的原因
          • 注意 : 以上代码是有问题 , 接下来继续改进
          • 通过上述代码的执行结果 , 发现了出现了负号票 , 和相同的票 . 说明数据出现安全问题
      • 1.2 线程的同步
      • 1.3 同步代码块
      • 1.4 同步方法
      • 1.5 Lock锁


在这里插入图片描述

1.1 线程安全产生的原因

  • 多个线程在对共享数据进行读改写的时候,可能导致的数据错乱就是线程的安全问题了
/*
    电影院
 */
public class Ticket implements Runnable {
    private int ticketCount = 100; // 一共有一百张票

    @Override
    public void run() {
        while (true) {
            // 如果票的数量为0 , 那么停止买票
            if (ticketCount == 0) {
                break;
            } else {
                // 有剩余的票 , 开始卖票
                ticketCount--;
                System.out.println(Thread.currentThread().getName() + "卖出一张票,剩下" + ticketCount + "张");
            }
        }
    }
}

/*
    1 定义一个类Ticket实现Runnable接口,里面定义一个成员变量:private int ticketCount = 100;
    2 在Ticket类中重写run()方法实现卖票,代码步骤如下
        A:判断票数大于0,就卖票,并告知是哪个窗口卖的
        B:票数要减1
        C:卖光之后,线程停止
    3 定义一个测试类TicketDemo,里面有main方法,代码步骤如下
        A:创建Ticket类的对象
        B:创建三个Thread类的对象,把Ticket对象作为构造方法的参数,并给出对应的窗口名称
        C:启动线程

 */
public class TicketDemo {
    public static void main(String[] args) {
        // 创建任务类对象
        Ticket ticket = new Ticket();

        // 创建三个线程类对象
        Thread t1 = new Thread(ticket);
        Thread t2 = new Thread(ticket);
        Thread t3 = new Thread(ticket);
        // 给三个线程命名
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        // 开启三个线程
        t1.start();
        t2.start();
        t3.start();
    }
}

注意 : 以上代码是有问题 , 接下来继续改进
  • 因为出票是有时间的 , 所有现在在每次买票之前, 休眠100毫秒 , 尝试执行代码
/*
    电影院
 */
public class Ticket implements Runnable {
    private int ticketCount = 100; // 一共有一百张票

    @Override
    public void run() {
        while (true) {
            // 如果票的数量为0 , 那么停止买票
            if (ticketCount <= 0) {
                break;
            } else {

                // 模拟出票的时间
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                // 有剩余的票 , 开始卖票
                ticketCount--;
                System.out.println(Thread.currentThread().getName() + "卖出一张票,剩下" + ticketCount + "张");
            }
        }
    }
}

/*
    1 定义一个类Ticket实现Runnable接口,里面定义一个成员变量:private int ticketCount = 100;
    2 在Ticket类中重写run()方法实现卖票,代码步骤如下
        A:判断票数大于0,就卖票,并告知是哪个窗口卖的
        B:票数要减1
        C:卖光之后,线程停止
    3 定义一个测试类TicketDemo,里面有main方法,代码步骤如下
        A:创建Ticket类的对象
        B:创建三个Thread类的对象,把Ticket对象作为构造方法的参数,并给出对应的窗口名称
        C:启动线程

 */
public class TicketDemo {
    public static void main(String[] args) {
        // 创建任务类对象
        Ticket ticket = new Ticket();

        // 创建三个线程类对象
        Thread t1 = new Thread(ticket);
        Thread t2 = new Thread(ticket);
        Thread t3 = new Thread(ticket);
        // 给三个线程命名
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        // 开启三个线程
        t1.start();
        t2.start();
        t3.start();
    }
}

  • 通过上述代码的执行结果 , 发现了出现了负号票 , 和相同的票 . 说明数据出现安全问题
    • 问题出现的原因 : 多个线程在对共享数据进行读改写的时候,可能导致的数据错乱就是线程的安全问题了

1.2 线程的同步

  • 概述 : java允许多线程并发执行,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证该变量的唯一性和准确性

  • 分类

    • 同步代码块
    • 同步方法
    • 锁机制。Lock

1.3 同步代码块

同步代码块 : 锁住多条语句操作共享数据,可以使用同步代码块实现

第一部分 : 格式
           synchronized(任意对象) {
               多条语句操作共享数据的代码
           }

第二部分 : 注意
           1 默认情况锁是打开的,只要有一个线程进去执行代码了,锁就会关闭
           2 当线程执行完出来了,锁才会自动打开

第三部分 : 同步的好处和弊端
            好处 : 解决了多线程的数据安全问题
            弊端 : 当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
public class Ticket implements Runnable {
    private int ticketCount = 100; // 一共有一百张票

    @Override
    public void run() {
        while (true) {
            synchronized (Ticket.class) {
                // 如果票的数量为0 , 那么停止买票
                if (ticketCount <= 0) {
                    break;
                } else {
                    // 模拟出票的时间
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 有剩余的票 , 开始卖票
                    ticketCount--;
                    System.out.println(Thread.currentThread().getName() + "卖出一张票,剩下" + ticketCount + "张");
                }
            }
        }
    }
}
/*
    1 定义一个类Ticket实现Runnable接口,里面定义一个成员变量:private int ticketCount = 100;
    2 在Ticket类中重写run()方法实现卖票,代码步骤如下
        A:判断票数大于0,就卖票,并告知是哪个窗口卖的
        B:票数要减1
        C:卖光之后,线程停止
    3 定义一个测试类TicketDemo,里面有main方法,代码步骤如下
        A:创建Ticket类的对象
        B:创建三个Thread类的对象,把Ticket对象作为构造方法的参数,并给出对应的窗口名称
        C:启动线程

 */
public class TicketDemo {
    public static void main(String[] args) {
        // 创建任务类对象
        Ticket ticket = new Ticket();

        // 创建三个线程类对象
        Thread t1 = new Thread(ticket);
        Thread t2 = new Thread(ticket);
        Thread t3 = new Thread(ticket);
        // 给三个线程命名
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        // 开启三个线程
        t1.start();
        t2.start();
        t3.start();
    }
}

1.4 同步方法

同步方法:就是把synchronized关键字加到方法上

格式:修饰符 synchronized 返回值类型 方法名(方法参数) {    }

同步代码块和同步方法的区别:
    1 同步代码块可以锁住指定代码,同步方法是锁住方法中所有代码
    2 同步代码块可以指定锁对象,同步方法不能指定锁对象

注意 : 同步方法时不能指定锁对象的 , 但是有默认存在的锁对象的。
    1 对于非static方法,同步锁就是this。
    2 对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。   Class类型的对象
/*
    同步方法:就是把synchronized关键字加到方法上

    格式:修饰符 synchronized 返回值类型 方法名(方法参数) {    }

    同步代码块和同步方法的区别:
        1 同步代码块可以锁住指定代码,同步方法是锁住方法中所有代码
        2 同步代码块可以指定锁对象,同步方法不能指定锁对象

    注意 : 同步方法时不能指定锁对象的 , 但是有默认存在的锁对象的。
        1 对于非static方法,同步锁就是this。
        2 对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。   Class类型的对象

 */
public class Ticket implements Runnable {
    private int ticketCount = 100; // 一共有一百张票

    @Override
    public void run() {
        while (true) {
            if (method()) {
                break;
            }
        }
    }

    private synchronized boolean method() {
        // 如果票的数量为0 , 那么停止买票
        if (ticketCount <= 0) {
            return true;
        } else {
            // 模拟出票的时间
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // 有剩余的票 , 开始卖票
            ticketCount--;
            System.out.println(Thread.currentThread().getName() + "卖出一张票,剩下" + ticketCount + "张");
            return false;
        }
    }
}

/*
    1 定义一个类Ticket实现Runnable接口,里面定义一个成员变量:private int ticketCount = 100;
    2 在Ticket类中重写run()方法实现卖票,代码步骤如下
        A:判断票数大于0,就卖票,并告知是哪个窗口卖的
        B:票数要减1
        C:卖光之后,线程停止
    3 定义一个测试类TicketDemo,里面有main方法,代码步骤如下
        A:创建Ticket类的对象
        B:创建三个Thread类的对象,把Ticket对象作为构造方法的参数,并给出对应的窗口名称
        C:启动线程

 */
public class TicketDemo {
    public static void main(String[] args) {
        // 创建任务类对象
        Ticket ticket = new Ticket();

        // 创建三个线程类对象
        Thread t1 = new Thread(ticket);
        Thread t2 = new Thread(ticket);
        Thread t3 = new Thread(ticket);
        // 给三个线程命名
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        // 开启三个线程
        t1.start();
        t2.start();
        t3.start();
    }
}

1.5 Lock锁

虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

Lock中提供了获得锁和释放锁的方法
    void lock():获得锁
    void unlock():释放锁

Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
    ReentrantLock的构造方法
    ReentrantLock():创建一个ReentrantLock的实例

注意:多个线程使用相同的Lock锁对象,需要多线程操作数据的代码放在lock()和unLock()方法之间。一定要确保unlock最后能够调用
import java.util.concurrent.locks.ReentrantLock;

/*
    虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,
    为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

    Lock中提供了获得锁和释放锁的方法
        void lock():获得锁
        void unlock():释放锁

    Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
        ReentrantLock的构造方法
        ReentrantLock​():创建一个ReentrantLock的实例

    注意:多个线程使用相同的Lock锁对象,需要多线程操作数据的代码放在lock()和unLock()方法之间。一定要确保unlock最后能够调用

 */
public class Ticket implements Runnable {
    private int ticketCount = 100; // 一共有一百张票
    ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                lock.lock();// 加锁
                // 如果票的数量为0 , 那么停止买票
                if (ticketCount <= 0) {
                    break;
                } else {
                    // 模拟出票的时间
                    Thread.sleep(100);
                    // 有剩余的票 , 开始卖票
                    ticketCount--;
                    System.out.println(Thread.currentThread().getName() + "卖出一张票,剩下" + ticketCount + "张");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();// 释放锁
            }
        }
    }
}

/*
    1 定义一个类Ticket实现Runnable接口,里面定义一个成员变量:private int ticketCount = 100;
    2 在Ticket类中重写run()方法实现卖票,代码步骤如下
        A:判断票数大于0,就卖票,并告知是哪个窗口卖的
        B:票数要减1
        C:卖光之后,线程停止
    3 定义一个测试类TicketDemo,里面有main方法,代码步骤如下
        A:创建Ticket类的对象
        B:创建三个Thread类的对象,把Ticket对象作为构造方法的参数,并给出对应的窗口名称
        C:启动线程

 */
public class TicketDemo {
    public static void main(String[] args) {
        // 创建任务类对象
        Ticket ticket = new Ticket();

        // 创建三个线程类对象
        Thread t1 = new Thread(ticket);
        Thread t2 = new Thread(ticket);
        Thread t3 = new Thread(ticket);
        // 给三个线程命名
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        // 开启三个线程
        t1.start();
        t2.start();
        t3.start();
    }
}



在这里插入图片描述



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

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

相关文章

关系(三)利用python绘制相关矩阵图

关系&#xff08;三&#xff09;利用python绘制相关矩阵图 相关矩阵图&#xff08;Correlogram&#xff09;简介 相关矩阵图既可以分析每对变量之间的相关性&#xff0c;也可以分析单变量的分布情况。相关性以散点图的形式可视化&#xff0c;对角线用直方图/密度图表示每个变量…

MySQL 底层数据结构 聚簇索引以及二级索引 Explain的使用

数据结构 我们知道MySQL的存储引擎Innodb默认底层是使用B树的变种来存储数据的 下面我们来复习一下B树存储 B树存储 哈希存储的区别 哈希存储,只能使用等值查询 B树与B树存储 我们知道B树实际上就是B树的变种 那么为啥使用B树而不是使用B树呢? 我们知道效率的高低主要取决于…

【JavaScript】作用域和闭包

作用域 作用域是程序源代码中定义的范围。JavaScript采用词法作用域&#xff0c;也就是静态作用域。所谓词法作用域就是在函数定义的时候就已经确定了。 let value 1 function foo(){console.log(value) } function bar(){let value 2foo() } bar() // 1变量对象是当前代码…

2.基础乐理-唱名的来历,简谱的构造

前置内容&#xff1a;1.唱名与记住唱名的方法 唱名的来历&#xff1a; 很久很久以前&#xff08;公元前&#xff09;各个文明开始诞生和慢慢发展&#xff0c;随着文明的发展&#xff0c;各个文明都开始出现自己的音乐&#xff0c;根据考古学家的发现在 公元前1800年&#xff…

【随笔】Git 高级篇 -- 整理提交记录(下)rebase(十六)

&#x1f48c; 所属专栏&#xff1a;【Git】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#x1f496; 欢迎大…

每日汇评:地缘冲突休战会阻碍黄金创纪录的涨势吗?

尽管以色列与哈马斯的地缘政治紧张局势有所缓和&#xff0c;金价仍冲破 2350 美元&#xff0c;创下新高&#xff1b; 对央行黄金购买量激增的预期抵消了美国非农就业数据主导的美联储强硬押注&#xff1b; 在美国CPI数据公布之前&#xff0c;由于 RSI 极度超买&#xff0c;金价…

YOLOV8注意力改进方法: CoTAttention(Visual Question Answering,VQA)附改进代码)

原论文地址&#xff1a;原论文下载地址 论文相关内容介绍&#xff1a; 论文摘要翻译&#xff1a; 具有自关注的Transformer导致了自然语言处理领域的革命&#xff0c;并且最近在许多计算机视觉任务中激发了具有竞争性结果的Transformer风格架构设计的出现。然而&#xff0c;大…

day02php环境和编译器—我耀学IT

一、环境介绍 1、web 环境 使用 PHP 需要先安装环境&#xff0c;安装环境比较麻烦&#xff0c;需要安装Web服务、PHP应用服务器、MySQL管理系统。 Web服务&#xff1a;apache 和 nginx PHP&#xff1a;多版本 MySQL&#xff1a;多版本 2、环境集成包 因为多环境、多版本、多系…

查看Linux系统相关信息

注&#xff1a; 本文只是博主学习记录分享&#xff0c;仅供参考。如有错误肯定是博主理解有问题&#xff0c;谢谢&#xff01; 一、查看当前操作系统的内核信息 命令&#xff1a;uname -a 二、查看当前操作系统版本信息 命令&#xff1a;cat /proc/version 这条命令可以查看到…

什么是电子邮件加密?电子邮件加密有几种类型?

在当今数字时代&#xff0c;电子邮件已经成为人们日常生活中不可或缺的沟通工具&#xff0c;据相关数据统计&#xff0c;全球每天发送的电子邮件数以亿计&#xff0c;而这些邮件里面通常包含了姓名、地址、个人身份信息 (PII)、登录凭证、财务信息、法律合同、知识产权等有价值…

Python学习从0到1 day22 第二阶段 面向对象 ⑤ 多态

诋毁本身就是一种仰望 —— 24.4.8 学习目标 1.理解多态的概念 2.理解抽象类&#xff08;接口的编程思想&#xff09; 多态 多态&#xff0c;指的是&#xff1a;多种状态&#xff0c;即完成某个行为时&#xff0c;使用不同的对象会得到不同的状态 多态常用在继承关系上 比如&am…

从事「音视频领域」开发工作有前途吗?

据我所知&#xff0c;做音视频的也分算法和工程&#xff0c;这两者都比普通开发高出不少&#xff0c;音视频工程开发的薪资大概比普通web开发高出30%。在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「音视频开发的资料从专业入门到高级教程」&#xff0…

使用 AI 生成正则表达式,告别正则烦恼

如果你有处理正则表达式的需求&#xff0c;那么这个网站&#xff08;autoregex.xyz&#xff09;一定要收藏好。 可以根据文字描述生成正则表达式。 默认是从文字到正则&#xff0c;不用选择。 输入框中输入描述&#xff0c;点击 ”GO“ 按钮。 等待一会儿&#xff0c;即可生…

计算 Hermitian 矩阵的特征值和特征向量 cusolver 示例 DnCheevj

1&#xff0c;原理 计算Hermitian 矩阵的特征值&#xff0c;使用Jacobi 旋转法&#xff0c;每次调整两个对称元素为0&#xff0c;通过迭代&#xff0c;使得非对角线上的值总体越来越趋近于0. 示例扩展了 nv 的 cusolverDsyevj 的示例 由于特征向量是正交的&#xff0c;故V*V…

预训练任务

预训练任务 - Mask Language Model jieba预分词长度小于4的词直接mask&#xff08;mask_ids就是input_ids&#xff09; if rands > self.mask_rate and len(word) < 4:word word_list[i]word_encode tokenizer.encode(word, add_special_tokensFalse)for token in wo…

Leetcode面试经典150_Q122买卖股票的最佳时机II

题目&#xff1a; 给你一个整数数组 prices &#xff0c;其中 prices[i] 表示某支股票第 i 天的价格。 在每一天&#xff0c;你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买&#xff0c;然后在 同一天 出售。 返回 你能获得的 最大…

快速为App打造Android端聊天室,节省80%开发成本(一)

前言 环信 ChatroomUIKit 提供 UIKit 的各种组件&#xff0c;能帮助开发者根据实际业务需求快速搭建聊天室应用&#xff0c;有效节约开发成本&#xff01;通过该 UIKit&#xff0c;聊天室中的用户可实时交互&#xff0c;发送普通弹幕消息、打赏消息和全局广播等功能。 本文详…

紫叶写作能用吗 #微信#知识分享

紫叶写作是一款非常好用、靠谱的论文写作工具&#xff0c;它旨在帮助用户快速高效地完成论文写作任务&#xff0c;并提供查重降重的功能。它不仅操作简单方便&#xff0c;而且功能强大&#xff0c;能够有效提高论文写作的效率和质量。 首先&#xff0c;紫叶写作提供了丰富的模板…

10款白嫖党必备的ai写作神器,你都知道吗? #媒体#人工智能#其他

从事自媒体运营光靠自己手动操作效率是非常低的&#xff0c;想要提高运营效率就必须要学会合理的使用一些辅助工具。下面小编就跟大家分享一些自媒体常用的辅助工具&#xff0c;觉得有用的朋友可以收藏分享。 1.飞鸟写作 这是一个微信公众号 面向专业写作领域的ai写作工具&am…

jmeter压测websocket协议

一、jmeter 安装websocket插件 1、选项--插件管理 2、搜索WebSocket Samplers by Peter Doornbosch插件 进行安装 3、 重启 jmeter 二、jmeter压测websocket协议实战 2.1、以网站为例&#xff1a; websocket在线测试 1、断开连接 2、打开F12&#xff0c;查看WS数据 3、…