并发编程之一些多线程习题的详细解析

简介: 并发编程之一些多线程习题的详细解析

4.5 习题

卖票练习、

测试下面代码是否存在线程安全问题,并尝试改正

  • 将sell方法声明为synchronized即可
  • 注意只将对count进行修改的一行代码用synchronized括起来也不行。对count大小的判断也必须是为原子操作的一部分,否则也会导致count值异常。
public class ExerciseSell {
    public static void main(String[] args) {
        TicketWindow ticketWindow = new TicketWindow(2000);
        List<Thread> list = new ArrayList<>();
        // 用来存储买出去多少张票
        List<Integer> sellCount = new Vector<>();
        for (int i = 0; i < 2000; i++) {
            Thread t = new Thread(() -> {
                // 分析这里的竞态条件
                int count = ticketWindow.sell(randomAmount());
                sellCount.add(count);
            });
            list.add(t);
            t.start();
        }
        list.forEach((t) -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        // 买出去的票求和
        log.debug("selled count:{}",sellCount.stream().mapToInt(c -> c).sum());
        // 剩余票数
        log.debug("remainder count:{}", ticketWindow.getCount());
    }
    // Random 为线程安全
    static Random random = new Random();
    // 随机 1~5
    public static int randomAmount() {
        return random.nextInt(5) + 1;
    }
}
class TicketWindow {
    private int count;
    public TicketWindow(int count) {
        this.count = count;
    }
    public int getCount() {
        return count;
    }
    //在方法上加一个synchronized即可
    public int sell(int amount) {
        if (this.count >= amount) {
            this.count -= amount;
            return amount;
        } else {
            return 0;
        }
    }
}    

另外,用下面的代码行不行,为什么?

  • 不行,因为sellCount会被多个线程共享,必须使用线程安全的实现类。
List<Integer> sellCount = new ArrayList<>();

测试脚本

for /L %n in (1,1,10) do java -cp ".;C:\Users\manyh\.m2\repository\ch\qos\logback\logback•classic\1.2.3\logback-classic-1.2.3.jar;C:\Users\manyh\.m2\repository\ch\qos\logback\logback•core\1.2.3\logback-core-1.2.3.jar;C:\Users\manyh\.m2\repository\org\slf4j\slf4j•api\1.7.25\slf4j-api-1.7.25.jar" cn.itcast.n4.exercise.ExerciseSell

说明:

  • 两段没有前后因果关系的临界区代码,只需要保证各自的原子性即可,不需要括起来。
转账练习

测试下面代码是否存在线程安全问题,并尝试改正

  • 将transfer方法的方法体用同步代码块包裹,将当Account.class设为锁对象。
public class ExerciseTransfer {
    public static void main(String[] args) throws InterruptedException {
        Account a = new Account(1000);
        Account b = new Account(1000);
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                a.transfer(b, randomAmount());
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                b.transfer(a, randomAmount());
            }
        }, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        // 查看转账2000次后的总金额
        log.debug("total:{}",(a.getMoney() + b.getMoney()));
    }
    // Random 为线程安全
    static Random random = new Random();
    // 随机 1~100
    public static int randomAmount() {
        return random.nextInt(100) +1;
    }
}
class Account {
    private int money;
    public Account(int money) {
        this.money = money;
    }
    public int getMoney() {
        return money;
    }
    public void setMoney(int money) {
        this.money = money;
    }
    public void transfer(Account target, int amount) {
        if (this.money > amount) {
            this.setMoney(this.getMoney() - amount);
            target.setMoney(target.getMoney() + amount);
        }
    }
}

这样改正行不行,为什么?

  • 不行,因为不同线程调用此方法,将会锁住不同的对象
public synchronized void transfer(Account target, int amount) {
    if (this.money > amount) {
        this.setMoney(this.getMoney() - amount);
        target.setMoney(target.getMoney() + amount);
    }
}


相关文章
|
11月前
|
Web App开发 移动开发 前端开发
【前端基础篇】HTML零基础速通1
【前端基础篇】HTML零基础速通
110 1
|
Java Apache Maven
【异常解决】Handler dispatch failed;nested exception is java.lang.NoClassDefFoundError: org/apache/common
【异常解决】Handler dispatch failed;nested exception is java.lang.NoClassDefFoundError: org/apache/common
7743 0
|
前端开发
CSS | 我看谁还不用这个UI库
最近新开不少项目,又实在是不想自己去定义特别多的 css 公共样式了,毕竟人多不好维护。于是找到了这个 tailwindcss UI 库。主要是内置了很多常用的 css 样式,前期避免了大量时间去写 css,中间开发也不用专门去维护,方便的一批。
404 0
|
Web App开发 JavaScript 前端开发
如何用vite+vue-next快速开发chrome插件
之前写过一篇文章《从开发chrome插件到插件系统设计》,主要讲述了如何开发一个chrome插件和chrome插件设计,感兴趣的同学可以再去看看。
1229 0
|
域名解析 前端开发 Java
企微配置可信域名
企微配置可信域名
2080 0
|
设计模式 前端开发 JavaScript
如何通俗地理解「分布式系统」;Vue是否可以在一个项目中使用多个UI框架;大厂上线流程:先上前端还是后端|极客观点
如何通俗地理解「分布式系统」;Vue是否可以在一个项目中使用多个UI框架;大厂上线流程:先上前端还是后端|极客观点
434 0
|
存储 缓存 Java
JUC并发编程学习(十)-阻塞队列、同步队列
JUC并发编程学习(十)-阻塞队列、同步队列
JUC并发编程学习(十)-阻塞队列、同步队列
|
存储 Java
揭秘 Java JVM 中的 Eden 区:对象分配、垃圾回收与性能优化
在 Java 虚拟机(JVM)的内存管理中,Eden 区是新生代内存的一部分,负责存储新创建的对象。了解 Eden 区的分配规则、垃圾回收策略以及性能优化方法,对于构建高性能、低延迟的 Java 应用至关重要。本文将深入探讨 Eden 区的原理、工作机制和性能优化,助您更好地管理内存资源。
|
数据安全/隐私保护 芯片
ps2022 for Mac激活版一键安装,Photoshop 2022 v23.2 ACR14.2下载安装教程
ps2022 for Mac激活版一键安装,Photoshop 2022 v23.2 ACR14.2下载安装教程
ps2022 for Mac激活版一键安装,Photoshop 2022 v23.2 ACR14.2下载安装教程
|
前端开发 IDE 开发工具
「趣学前端」优雅又精致,来看看别人家的表格样式是怎样实现
用技术实现梦想,用梦想打开创意之门。今天分享前端CSS中的表格的知识点。
373 0