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

文章目录

    • (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

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

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

相关文章

实现单点登录

指再多系统应用群中登录一个系统&#xff0c;便可在其他所有系统中得到授权而无需再次登录&#xff0c;包括单点登录与单点注销两部分。 相比于单系统登录&#xff0c;sso需要一个独立的认证中心&#xff0c;只有认证红心能接受用户的用户名密码等安全信息&#xff0c;其他系统…

N65总账凭证管理凭证查询(sql)

--核算账簿 select code , name , pk_setofbook from org_setofbook where ( pk_setofbook in ( select pk_setofbook from org_accountingbook where 1 1 and ( pk_group N0001A11000000000037X ) and ( accountenablestate 2 ) ) ) order by code;--核算账簿 select code …

VMware虚拟机安装macOS

VMware虚拟机安装macOS 文章目录 VMware虚拟机安装macOS先看效果一、准备工作①&#xff1a;镜像资源下载②&#xff1a;虚拟机③&#xff1a;安装macOS所必要的插件 二、开始安装①&#xff1a;创建新的虚拟机②&#xff1a;自定义硬件③&#xff1a;开启虚拟机 先看效果 一、…

Packet tracer-实现VLAN内部通信

案例一&#xff1a; 要求PC1和PC2&#xff0c;PC3和PC4之间能够实现互访 两个VLAN&#xff0c;一个VLAN对应一个子网 以S2为例&#xff1a; 步骤 1&#xff1a;在 S2 上创建并命令 VLAN&#xff0c;把VLAN划分给活动的端口。 步骤 2&#xff1a;在 S3 上创建并命令 VLAN&…

在 WLC上配置WPA2-Enterprise WLAN

实验大纲 第1部分&#xff1a;创建一个新的WLAN 第1步&#xff1a;创建一个新的VLAN接口 第2步&#xff1a;配置WLC让它使用RADIUS服务器 第3步&#xff1a;创建一个新的WLAN 第4步&#xff1a;配置WLAN安全策略 第2部分&#xff1a;配置DHCP范围和SNMP 第1步&#xff1…

软件测试相关内容第二弹--软件测相关概念

学习完这部分内容后&#xff0c;可以掌握测试相关基础概念&#xff0c;掌握常见的开发模型、测试模型。 主要进行四个部分内容的学习&#xff1a;需求、bug、测试用例、开发模型和测试模型。 目录 1. 什么是需求 1.1 需求的定义 1.2 为什么有需求 1.3 测试人员眼里的需求…

ElementUI Form:Switch 开关

ElementUI安装与使用指南 Switch 开关 点击下载learnelementuispringboot项目源码 效果图 el-switch.vue 页面效果图 项目里el-switch.vue代码 <script> export default {name: el_switch,data() {return {value: true,value1: true,value2: true,value3: 100,value…

Nginx 部署指定文件夹下的项目(本地测试)

1、配置 vue.config.js&#xff0c;指定生成环境的包 //部署生产环境和开发环境下的URLpublicPath: process.env.NODE_ENV production ? "/marketing" : "/",///npm run build 或 varn build 生成文件的日录名称(要利baseUrl的牛产环境路一致)(默认dist…

三、Redis之数据类型

3.1 Key操作 3.1.1 相关命令 序号命令语法描述1DEL key该命令用于在 key 存在时删除 key2DUMP key序列化给定 key &#xff0c;并返回被序列化的值3EXISTS key检查给定 key 是否存在&#xff0c;存在返回1&#xff0c;否则返回04EXPIRE key seconds为给定 key 设置过期时间&a…

Redis简单阐述、安装配置及远程访问

目录 一、Redis简介 1.什么是Redis 2.特点 3.优势 4.数据库对比 5.应用场景 二、 安装与配置 1.下载 2.上传解压 3.安装gcc 4.编译 5.查看安装目录 6.后端启动 7.测试 8.系统服务配置 三、Redis远程访问 1.修改访问IP地址 2.设置登录密码 3.重启Redis服务 …

二次元插画风生图咒语

中文&#xff1a;一个可爱的卡通女孩穿着漂亮的毛衣&#xff0c;抱着一只可爱的小狗&#xff0c;全身&#xff0c;白色背景&#xff0c;粉色和蓝色&#xff0c;基思哈林风格的涂鸦&#xff0c;Sharpie插图&#xff0c;MBE插图&#xff0c;粗体线条&#xff0c;垃圾美风格&#…

Verdi简介

3.1.1 Verdi的历史 相信做IC验证的朋友或多或少都使用过VCS和Verdi这两个工具&#xff0c;这两个工具目前都属于synopsys公司&#xff0c;但是Verdi的来源可谓一路坎坷。 Verdi最开始是由novas公司设计的&#xff0c;在2008年&#xff0c;被台湾的EDA厂家springsoft&#xff08…

MySQL基础知识(二)

MySQL基础知识&#xff08;二&#xff09; 一、MySQL简介 MySQL 是一个关系型数据库管理系统&#xff0c; 由瑞典 MySQL AB 公司开 发&#xff0c; 目前属于 Oracle 公司。MySQL 是一种关系型数据库管理系 统&#xff0c;关系型数据库将数据保存在不同的表 中&#xff0c;而…

微信小程序如何实现实时显示输入内容

如下所示&#xff0c;在许多场景中需要实时显示用户输入&#xff0c;具体实现见下文。 .wxml <input type"text" placeholder"请输入{{item.value}}(必填)" style"width:80%;" bindinput"get_required_value" data-info"{{it…

壹[1],Xamarin开发

1&#xff0c;环境 VS2022 注&#xff1a; 1&#xff0c;本来计划使用AndroidStudio&#xff0c;但是也是一堆莫名的配置让人搞得很神伤&#xff0c;还是回归C#。 2&#xff0c;MAUI操作类似&#xff0c;但是很多错误解来解去&#xff0c;且调试起来很卡。 3&#xff0c;最…

LeetCode —— 137. 只出现一次的数字 II

&#x1f636;‍&#x1f32b;️&#x1f636;‍&#x1f32b;️&#x1f636;‍&#x1f32b;️&#x1f636;‍&#x1f32b;️Take your time ! &#x1f636;‍&#x1f32b;️&#x1f636;‍&#x1f32b;️&#x1f636;‍&#x1f32b;️&#x1f636;‍&#x1f32b;️…

C++类和对象(中)六个默认成员函数

&#x1f308;类的六个默认成员函数 任何一个类&#xff0c;不管是否为空&#xff0c;都会在生成的时候默认调用六个成员函数&#xff0c;这些成员函数可以自动生成&#xff0c;也可以由程序员写出。这六个默认成员函数分别是&#xff1a; 最主要的是前四个&#xff1a; 初始…

DDD学习使用

简介 DDD(Domain-Driven Design)&#xff1a;领域驱动设计。 Eric Evans “领域驱动设计之父” DDD不是架构&#xff0c;而是一种方法论&#xff08;Methodology&#xff09;微服务架构从一出来就没有很好的理论支撑如何合理的划分服务边界&#xff0c;人们常常为服务要划分多…

6.3 内存池模式

Bruce Powel Douglass大师介绍-CSDN博客https://blog.csdn.net/ChatCoding/article/details/134665868嵌入式软件开发从小工到专家-CSDN博客https://blog.csdn.net/ChatCoding/article/details/135297955C嵌入式编程设计模式源码-CSDN博客https://blog.csdn.net/ChatCoding/art…

Android 基础技术——Handler

笔者希望做一个系列&#xff0c;整理 Android 基础技术&#xff0c;本章是关于 Handler 为什么一个线程对应一个Looper&#xff1f; 核心&#xff1a;通过ThreadLocal保证 Looper.prepare的时候&#xff0c;ThreadLocal.get如果不空报异常&#xff1b;否则调用ThreadLocal.set,…