Java多线程--线程安全问题练习题

news2024/11/16 19:54:56

文章目录

    • (1)练习题1
    • (2)练习题2
    • (3)练习题3

现在咱们线程一共说了这么几件事情,如下:
image.png

具体文章见专栏。

接下来看几个练习题吧。

(1)练习题1

🌋题目描述

【新年倒计时】

模拟新年倒计时,每隔1秒输出一个数字,依次输出10,9,8…1,最后输出:新年快乐!

🍰分析

题目中没有说要造分线程,那我们可以直接放到主线程里面,也是可以的。

直接写一个for循环遍历即可,如下:

public class HappyNewYear {
    public static void main(String[] args) {
        for (int i = 10; i >=1 ; i--) {
            System.out.println(i);
        }
    }
}

然后sleep,让它一秒钟输出一下。如下:

image.png

记得处理一下异常:

image.png

这里只能用try-catch处理,因为sleep抛出的方法是编译时异常,而且父类没有抛出,不能使用throws的方式。

🌱代码

package yuyi03;

/**
 * ClassName: HappyNewYear
 * Package: yuyi03
 * Description:
 *  模拟新年倒计时,每隔1秒输出一个数字,依次输出10,9,8......1,最后输出:新年快乐!
 * @Author 雨翼轻尘
 * @Create 2024/1/30 0030 13:03
 */
public class HappyNewYear {
    public static void main(String[] args) {
        for (int i = 10; i >=0 ; i--) {
            try {
                Thread.sleep(1000); //睡1s
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if(i>0){
                System.out.println(i);
            }else {
                System.out.println("HappyNewYear!");
            }
        }
    }
}

🍺输出结果

Java.gif

(2)练习题2

🌋题目描述

关于Thread.sleep()方法的一个面试题:

如下的代码中sleep()执行后,到底是哪个线程进入阻塞状态了呢?

🌱代码

package yuyi03;

/**
 * ClassName: ThreadTest
 * Package: yuyi03
 * Description:
 *      如下的代码中sleep()执行后,到底是哪个线程进入阻塞状态了呢?
 * @Author 雨翼轻尘
 * @Create 2024/1/30 0030 13:16
 */

public class ThreadTest {
    public static void main(String[] args) {
        // 创建线程对象
        MyThread t = new MyThread();
        t.setName("线程1");
        t.start();

        // 调用sleep方法
        try {
            t.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 5秒之后这里才会执行。
        System.out.println("hello World!");
    }
}

class MyThread extends Thread {
    public void run() {
        for (int i = 0; i < 10000; i++) {
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
    }
}

🍺输出结果(部分)

image.png

🍰分析

main方法中造了一个MyThread的对象,并且起了一个名字,调用了start方法。

紧接着有一个sleep方法,这个sleep方法是让主线程睡了还是让分线程睡了?

其实是主线程

虽然t是一个线程,但是这里不能算是一个线程去调用sleep。只能理解为是一个对象去调用sleep。

t.sleep()代码的执行,是在主线程里面调用的。

静态方法,用类和对象调用都没有区别,都只会影响主线程

(3)练习题3

🌋题目描述

银行有一个账户。

有两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。

问题:该程序是否有安全问题,如果有,如何解决?

【提示】

1,明确哪些代码是多线程运行代码,须写入run()方法。

2,明确什么是共享数据。

3,明确多线程运行代码中哪些语句是操作共享数据的。

【拓展问题】可否实现两个储户交替存钱的操作

🍰分析

“同一个账户”就是共享数据

如果有多线程,那就一定会有线程安全问题吗?

不一定。比如一个线程操作一个账户的钱,另一个线程操作另一个账户的钱,这就不会有线程安全问题。

但若是多个线程操作一个共享数据,就会有线程安全问题了。


本题目我们用继承Thread的方式来写。

比如现在有一个客户,我们让它继承于Thread类。如下:

public class AccountTest {

}

class Customer extends Thread{

}

既然大家需要共用一个账户,那么这个账户如何体现共享?

用静态吗?

其实不用静态也可以!

比如现在声明一个Account类,就是账户类,里面有余额,如下:

class Account{  //账户
    private double balance; //余额
}

然后在客户Customer类里面声明一个Account属性,如下:

class Account{  //账户
    private double balance; //余额
}

class Customer extends Thread{
    Account account;
}

这里就不将它写成静态的了,那能不能实现共享呢?

这就取决于它的构造器如何去使用了。

通过构造器给当前属性实例化一下,如下:

class Account{  //账户
    private double balance; //余额
}

class Customer extends Thread{
    Account account;

    //构造器
    public Customer(Account acct){
        this.account=acct;
    }

}

怎么保证造两个Customer,是同一个account呢?

new一个对象,然后将值传进去即可。

比如在main方法中,造一个账户acct,然后new一个Customer的时候将acct传进去。

public class AccountTest {
    public static void main(String[] args) {
        Account acct=new Account();

        new Customer(acct);
    }
}

这样就可以创建两个Customer,如下:

public class AccountTest {
    public static void main(String[] args) {
        Account acct=new Account();

        Customer customer1=new Customer(acct);
        Customer customer2=new Customer(acct);
    }
}

现在这两个线程就共享同一个账户acct

以后就可以使用这种思路来让线程共享资源啦,如下:

public class AccountTest {
    public static void main(String[] args) {
        Account acct=new Account();

        Customer customer1=new Customer(acct);
        Customer customer2=new Customer(acct);
    }
}

class Account{  //账户
    private double balance; //余额
}

class Customer extends Thread{
    Account account;

    //构造器
    public Customer(Account acct){
        this.account=acct;
    }

}

同一个对象的实例变量是共享的。


在Thread类里面有一个可以起名字的构造器,这边就使用它来给线程起个名字吧。

来个重载构造器:

class Customer extends Thread{
    Account account;

    //构造器
    public Customer(Account acct,String name){
        super(name);
        this.account=acct;
    }

}

现在就可以用这个构造器了,比如:

public class AccountTest {
    public static void main(String[] args) {
        Account acct=new Account();

        Customer customer1=new Customer(acct,"小旺");
        Customer customer2=new Customer(acct,"小岁");
    }
}

这样的话,就通过构造器的方式将线程的名字赋值好了。


现在两个储户需要存钱,需要在run方法里面做这个事情。

存三次,就循环三次:

class Customer extends Thread{
    //...
    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {

        }
    }
}

然后就是操作账户的余额,让余额balance三次增加。加钱的事情可以定义在Account类里面。如下:

class Account{  //账户
    private double balance; //余额

    public void deposit(double amt){
        if(amt>0){
            balance+=amt;
        }
        System.out.println(Thread.currentThread().getName()+"存钱1000元,余额为:"+balance);
    }
}

然后在刚才的for循环里面可以调用一下deposit方法。

class Customer extends Thread{
	//...
    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            account.deposit(1000);
        }
    }
}

当我们调用run方法的时候,就会进入for循环,然后调用account的deposit方法,首先往里面存了1000块钱。

然后各自线程去调用start方法,他们会各自调用run方法,去存钱。如下:

public class AccountTest {
    public static void main(String[] args) {
        Account acct=new Account();

        Customer customer1=new Customer(acct,"小旺");
        Customer customer2=new Customer(acct,"小岁");

        customer1.start();
        customer2.start();
    }
}

🌱代码

package yuyi03;

/**
 * ClassName: AccountTest
 * Package: yuyi03
 * Description:
 *
 * @Author 雨翼轻尘
 * @Create 2024/1/30 0030 14:56
 */
public class AccountTest {
    public static void main(String[] args) {
        Account acct=new Account();

        Customer customer1=new Customer(acct,"小旺");
        Customer customer2=new Customer(acct,"小岁");

        customer1.start();
        customer2.start();
    }
}

class Account{  //账户
    private double balance; //余额

    public void deposit(double amt){
        if(amt>0){
            balance+=amt;
        }
        System.out.println(Thread.currentThread().getName()+"存钱1000元,余额为:"+balance);
    }
}

class Customer extends Thread{
    Account account;

    //构造器
    public Customer(Account acct){
        this.account=acct;
    }

    public Customer(Account acct,String name){
        super(name);
        this.account=acct;
    }

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            account.deposit(1000);
        }
    }
}

🍺输出结果

image.png


现在看着没有线程安全问题,接下来演示一下线程安全问题

共享数据就是“钱数”balance。

在这里加一个sleep

image.png

现在的代码:

public class AccountTest {
    public static void main(String[] args) {
        Account acct=new Account();

        Customer customer1=new Customer(acct,"小旺");
        Customer customer2=new Customer(acct,"小岁");

        customer1.start();
        customer2.start();
    }
}

class Account{  //账户
    private double balance; //余额

    public void deposit(double amt){
        if(amt>0){
            balance+=amt;
        }

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName()+"存钱1000元,余额为:"+balance);
    }
}

class Customer extends Thread{
    Account account;

    //构造器
    public Customer(Account acct){
        this.account=acct;
    }

    public Customer(Account acct,String name){
        super(name);
        this.account=acct;
    }

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            account.deposit(1000);
        }
    }
}

输出结果:

image.png

可以看到出现了问题。

这就是线程安全问题,怎么解决呢?

balance是共享数据,直接将操作写在deposit方法里面了,那么可以给这个方法直接加上synchronized吗?

image.png

能不能使用,取决于这个this是不是唯一的。

大家不要记“继承的方式this不唯一”,需要具体问题具体分析。

这个方法的调用者是Account类的对象,而Account类的对象只创建了一个,如下:

image.png

所以现在就是acct。既然是唯一的,那么线程就是安全的。

🌱代码

package yuyi03;

/**
 * ClassName: AccountTest
 * Package: yuyi03
 * Description:
 *
 * @Author 雨翼轻尘
 * @Create 2024/1/30 0030 14:56
 */
public class AccountTest {
    public static void main(String[] args) {
        Account acct=new Account();

        Customer customer1=new Customer(acct,"小旺");
        Customer customer2=new Customer(acct,"小岁");

        customer1.start();
        customer2.start();
    }
}

class Account{  //账户
    private double balance; //余额

    public synchronized void deposit(double amt){   //this:是唯一的,即为actt
        if(amt>0){
            balance+=amt;
        }

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName()+"存钱1000元,余额为:"+balance);
    }
}

class Customer extends Thread{
    Account account;

    //构造器
    public Customer(Account acct){
        this.account=acct;
    }

    public Customer(Account acct,String name){
        super(name);
        this.account=acct;
    }

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            account.deposit(1000);
        }
    }
}

🍺输出结果

image.png

这就是之前说的,同步方法可以使用在“继承Thread类的子类”中的场景了,只要对象是同一个,那就可以直接使用非静态同步方法了。

🎲现在的结果显示小旺存完之后,小岁才存钱,要想体现交互可以让他睡一下,如下:

image.png

🌱代码

package yuyi03;

/**
 * ClassName: AccountTest
 * Package: yuyi03
 * Description:
 *
 * @Author 雨翼轻尘
 * @Create 2024/1/30 0030 14:56
 */
public class AccountTest {
    public static void main(String[] args) {
        Account acct=new Account();

        Customer customer1=new Customer(acct,"小旺");
        Customer customer2=new Customer(acct,"小岁");

        customer1.start();
        customer2.start();
    }
}

class Account{  //账户
    private double balance; //余额

    public synchronized void deposit(double amt){   //this:是唯一的,即为actt
        if(amt>0){
            balance+=amt;
        }

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName()+"存钱1000元,余额为:"+balance);
    }
}

class Customer extends Thread{
    Account account;

    //构造器
    public Customer(Account acct){
        this.account=acct;
    }

    public Customer(Account acct,String name){
        super(name);
        this.account=acct;
    }

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            account.deposit(1000);
        }
    }
}

🍺输出结果

image.png

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

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

相关文章

会议会务报名签到互动抽奖视频图片直播公众号开发

会议会务报名签到互动抽奖视频图片直播公众号开发 现在越来越多的人进行网络签到&#xff0c;特别是大型的会务/展会&#xff0c;网络签到进场形式应用的越来越广泛也是发展的趋势&#xff1b;会助力智能会议管理系统&#xff1a;可以实现在线报名&#xff08;免费/付费/后台导…

结构体的学习

结构体与共用体&#xff0c;枚举 1.数据类型复习&#xff1a; 2结构体. eg&#xff1b;统计全校同学信息 需要记录的点--- 姓名&#xff0c;班级&#xff0c;性别&#xff0c;成绩&#xff0c;年龄 统计名字&#xff1a;char s[ ] [ 100 ] { "Tmo" } …

Linux ---- Shell编程三剑客之sed

一、sed工具概述 1、sed概念 Sed&#xff08;Stream EDitor&#xff09;是一个强大而简单的文本解析转换工具可以读取文本&#xff0c;并根据指定的条件对文本内容进行编辑&#xff08;删除、替换、添加、移动等&#xff09;&#xff0c;最后输出所有行或者仅输出处理的某些行…

ONLYOFFICE文档8.0现已发布,功能全新升级!

本文目录-ONLYOFFICE 前言&#xff1a;ONLYOFFICE介绍一、经济实惠的开源选择二、自主部署保证数据安全三、灵活的定制与集成四、强大的跨平台全面支持ONLYOFFICE文档8.0更新内容更新一&#xff1a;显示协作者头像更新二&#xff1a;插件UI界面更新更新三&#xff1a;可填写的P…

springboot 怎么设置局域网访问

如何配置Spring Boot应用以实现局域网访问 在开发一个Spring Boot应用时&#xff0c;我们通常会通过localhost来访问和测试我们的应用。但是&#xff0c;当我们想要在局域网中分享我们的应用&#xff0c;供其他设备访问时&#xff0c;仅仅使用localhost是不够的。本文将引导你…

数据据库八之 视图、触发器、事务

【零】准备数据 【1】创建表 &#xff08;1&#xff09;部门表 d_id是部门的编号d_name是部门的名字 # 确保表不存在 drop table if exists department; # 创建表 create table department( d_id int auto_increment primary key, d_name varchar(6) )auto_increment 501 …

市场复盘总结 20240131

仅用于记录当天的市场情况,用于统计交易策略的适用情况,以便程序回测 短线核心:不参与任何级别的调整,采用龙空龙模式 昨日主题投资 连板进级率 5/23 21.7% 二进三: 进级率低 50% 最常用的二种方法: 方法一:指标选股找强势股 select * from dbo.ResultAll where 入…

git diff查看比对两次不同时间点提交的异同

git diff查看比对两次不同时间点提交的异同 用 git diff命令&#xff1a; git diff commit-id-1 commit-id-2 不同commit-id在不同的时间点提交产生&#xff0c;因为也可以认为git diff是比对两个不同时间点的代码异同。 git diff比较不同commit版本的代码文件异同_git diff c…

postman用法

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、postman怎么使用json输出 总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 提示&#xff1a;以下是本篇文章正文内容&#xff0…

使用企业微信一年要花费多少钱?

从今天开始&#xff0c;大力将为大家呈现一个新的话题——“企微问答”&#xff0c;大家有什么问题可以回复我们的公众号&#xff0c;如果你是严肃认真的提问&#xff0c;我们会严肃认真的给你一个答案。今日问题 &#xff1a;用企业微信一年要花多少钱 使用企业微信要花多少钱…

洛谷 P3366 【模板】最小生成树

题目描述 如题&#xff0c;给出一个无向图&#xff0c;求出最小生成树&#xff0c;如果该图不连通&#xff0c;则输出 orz。 输入格式 第一行包含两个整数 N,M&#xff0c;表示该图共有 N 个结点和 M 条无向边。 接下来 M 行每行包含三个整数Xi​,Yi​,Zi​&#xff0c;表示…

力扣题目训练(6)

2024年1月30日力扣题目训练 2024年1月30日力扣题目训练367. 有效的完全平方数374. 猜数字大小383. 赎金信99. 恢复二叉搜索树105. 从前序与中序遍历序列构造二叉树51. N 皇后 2024年1月30日力扣题目训练 2024年1月30日第六天编程训练&#xff0c;今天主要是进行一些题训练&…

1 月 30 日算法练习-思维和贪心

文章目录 重复字符串翻硬币乘积最大 重复字符串 思路&#xff1a;判断是否能整除&#xff0c;如果不能整除直接退出&#xff0c;能整除每次从每组对应位置中找出出现最多的字母将其他值修改为它&#xff0c;所有修改次数即为答案。 #include<iostream> using namespace …

太美医疗冲刺港交所上市:融资“数据打架”,老虎基金提前退出

1月29日&#xff0c;浙江太美医疗科技股份有限公司&#xff08;下称“太美医疗”或“太美医疗科技”&#xff09;递交招股书&#xff0c;准备在港交所主板上市。特别说明的是&#xff0c;该公司曾于2021年12月29日在上海证券交易所科创板递交上市申请。 据贝多财经了解&#x…

容器算法迭代器初识

#include<iostream> using namespace std; #include<vector> //vetor容器存放内置数据类型 void test01() {//创建了一个vector容器&#xff0c;数组 vector<int> v;//向容器中插入数据v.push_back (10);//尾插 v.push_back (20);v.push_back (30);v.push_ba…

vue-Router 路由

一、什么是路由&#xff1f; 在Web开发中&#xff0c;路由&#xff08;Routing&#xff09;是指根据用户访问的URL来决定应用程序应该显示哪个页面或者加载哪个组件的过程。简单来说&#xff0c;路由就是将不同的URL映射到不同的功能模块或页面上。当用户点击链接或者直接输入…

ADI 配合 USRP 使用的相控阵天线 cn0566

相控阵天线 在这里插入图片描述

利用Excel批量生成SQL语句

根据对应的数据库结构书写模板SQL&#xff0c;如&#xff1a; INSERT INTO Person VALUES(1,吕布,25,男,13500000001)然后选择表格后的单元格&#xff0c;并复制SQL语句&#xff08;注意最前方有一个号&#xff0c;整个SQL用 “ ” 包裹&#xff09;&#xff1a; "INSER…

响应式Web开发项目教程(HTML5+CSS3+Bootstrap)第2版 例5-5 Canvas 绘制三角形

代码 <!doctype html> <html> <head> <meta charset"utf-8"> <title>Canvas 绘制三角形</title> </head><body><canvas id"cavsElem">您的浏览器不支持Canvas&#xff0c;请升级浏览器</canvas…

墨鱼AI导航系统源码/小白也能即拿即用+视频教程

(购买本专栏可免费下载栏目内所有资源不受限制,持续发布中,需要注意的是,本专栏为批量下载专用,并无法保证某款源码或者插件绝对可用,介意不要购买) 墨鱼AI导航源码/小白也能即拿即用+视频教程,AI目前大火,估计未来3年内都将是风口。本系统墨鱼AI导航,顺着AI的风口,…