线程安全之a++问题以及打印产生问题的数字

线程安全之a++问题以及打印产生问题的数字

  • a++导致线程安全问题

1. a++多线程下数据丢失问题

案例:定义一个全局变量count,2个线程交替执行1万次count++,查看count最终结果。

public class MultiThreadsError implements Runnable {

    static int count = 0;

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            count++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MultiThreadsError multiThreadsError = new MultiThreadsError();
        Thread thread1 = new Thread(multiThreadsError);
        Thread thread2 = new Thread(multiThreadsError);

        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();

        System.out.println(count);
    }
}

image-20220424110515105

程序结果:count不等于20000。

问题分析

count++ 是一个复合操作:读取 - 执行 - 写入,并且当前数据状态以来上一个数据状态

未命名文件

线程一读取count = 0,然后执行到第二步,count + 1,此时,操作系统将线程时间片交到线程二手上,线程二执行第一步读取count = 0;

当线程一和线程二都执行完成。count 结果为1

拓展

打印出count重复执行那几次?

上代码:

public class MultiThreadsError implements Runnable {

    static int count = 0;
    static boolean[] arr = new boolean[100000000];

    static AtomicInteger counter = new AtomicInteger();
    static AtomicInteger countFail = new AtomicInteger();

    CyclicBarrier o1 = new CyclicBarrier(2);
    CyclicBarrier o2 = new CyclicBarrier(2);

    @Override
    public void run() {
        arr[0] = true;
        for (int i = 0; i < 100000; i++) {
            try {
                o2.reset();
                // 等待2个线程一起到达,再一起出发
                o1.await();
            } catch (InterruptedException | BrokenBarrierException e) {
                throw new RuntimeException(e);
            }

            count++;

            try {
                // 等待2个线程一起到达,再一起出发
                o1.reset();
                o2.await();
            } catch (InterruptedException | BrokenBarrierException e) {
                throw new RuntimeException(e);
            }
            counter.incrementAndGet();
            synchronized (this) {
                // 先判断后执行,会产生线程竞争,导致数据错误。
                if (arr[count] && arr[count - 1]) {
                    System.out.println("fail number " + count);
                    countFail.incrementAndGet();
                } else {
                    arr[count] = true;
                }
            }
        }

    }

    public static void main(String[] args) throws InterruptedException {
        MultiThreadsError multiThreadsError = new MultiThreadsError();
        Thread thread1 = new Thread(multiThreadsError);
        Thread thread2 = new Thread(multiThreadsError);

        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();

        System.out.println(count);
        System.out.println("执行次数:" + counter.get());
        System.out.println("错误次数:" + countFail.get());
    }
}

结果:

image-20220424170044364

代码分析:

  1. 使用arr数组来标记count的状态,如果2个线程产生竞争,那么假设2个线程得到的值都是1,那么执行if(arr[count])的时候就可以打印出产生竞争的那个数字。
  2. 定义了2个同步器o1和o2,环绕在count++执行前后,消除count++产生竞争对后续的代码的影响。假设如果不用同步器,那么if判断为true,但是打印的数字不一定是产生竞争的数字。
  3. 使用synchronized将判断语句锁起来,消除先判断后执行产生的线程问题。
以下是基于 C# 的基础练习题目集合,涵盖了数组操作、循环结构、面向对象编程等多个方面。这些题目可以帮助初学者巩固基础知识并提升实际编码能力。 --- ### 题目列表 #### 数组与循环 1. 编写一个程序,输入三个整数并按从小到大的顺序输出它们[^1]。 2. 实现一个函数 `FindNumbersWithCondition`,找出指定范围内的所有满足条件的三位数(如水仙花数)。 3. 使用 `while` 循环实现判断某个范围内所有的水仙花数,并打印出来[^2]。 4. 创建一个二维数组,初始化为随机值,并计算其行列平均值。 5. 输入一组数字,统计其中正数、负数和零的数量。 #### 字符串处理 6. 编写一段代码,反转字符串中的字符顺序。 7. 判断给定字符串是否为回文字符串(忽略大小写和空格)。 8. 将一个句子拆分为单词,并统计每个单词出现的次数。 9. 替换字符串中的特定子串为另一个子串。 10. 计算两个字符串之间的最长公共前缀。 #### 条件语句与逻辑运算 11. 编写一个简单的计算器程序,支持加减乘除四种基本运算。 12. 判断某一年份是否为闰年。 13. 根据用户输入的成绩等级(A-F),输出对应的分数区间。 14. 设计一个猜数字游戏,提示用户输入直到猜测正确为止。 15. 编写一个程序,模拟石头剪刀布的游戏过程。 #### 函数与方法 16. 定义一个求阶乘的方法 `Factorial(int n)` 并测试。 17. 编写一个递归函数来计算斐波那契数列的第 N 项。 18. 实现冒泡排序算法,对整型数组进行升序排列。 19. 编写快速排序算法,用于对任意类型的数组进行排序。 20. 构造一个通用交换函数,可以交换两种不同数据类型的变量值。 #### 类与对象 21. 定义一个人类 `Person`,包含姓名、年龄属性以及打招呼的方法 `SayHi()`[^4]。 22. 扩展上一题的人类,增加继承关系,派生出学生类 `Student` 和教师类 `Teacher`。 23. 调用基类构造器,在派生类中重写虚方法[^3]。 24. 实现抽象类的功能,设计一个多态场景下的方法调用。 25. 使用接口定义多个类的行为规范,并验证其实现。 #### 文件读写 26. 编写一个程序,将用户的输入保存到文件中。 27. 从文件中读取内容,并逐行显示在控制台上。 28. 复制一个文本文件的内容到另一个新文件中。 29. 统计文本文件中的总字数、行数和单词数量。 30. 过滤掉文件中的敏感词,并生成一个新的净化版本。 #### 异常处理 31. 捕获并处理除法运算中的被零除异常。 32. 在文件读写过程中加入异常捕获机制,防止路径错误等问题。 33. 自定义一种异常类型,并在适当位置抛出该异常。 34. 测试多种可能发生的异常情况,记录日志以便后续分析。 35. 结合数据库访问操作,演示如何优雅地处理 SQL 查询失败的情况。 #### LINQ 与集合 36. 使用 Lambda 表达式筛选出列表中大于某一阈值的所有元素。 37. 对 List<T> 中的数据按照某种规则分组并汇总结果。 38. 查找 Dictionary<K,V> 中键值最大的条目及其对应值。 39. 合并两个有序数组成新的有序数组。 40. 应用 LINQ 查询语法完成复杂查询任务。 #### 线程与并发 41. 创建多线程环境,分别执行不同的独立任务。 42. 使用锁机制保护共享资源免受竞争条件影响。 43. 展示异步编程模型的优势,通过 Task 或 async/await 关键字实现。 44. 设置定时器触发事件,每隔固定时间间隔运行一次回调函数。 45. 解决生产者消费者问题,利用队列协调两者的工作节奏。 #### 图形界面开发 46. 开发一个简易窗口应用程序,允许用户点击按钮改变背景颜色。 47. 添加菜单栏功能至 GUI 工具集中,提供退出选项。 48. 显示动态进度条控件,反映后台耗时工作的进展状态。 49. 接收键盘快捷键指令,响应相应的 UI 动作。 50. 整合图表库绘制折线图展示统计数据变化趋势。 ... 由于篇幅有限,仅列举部分典型题目作为示范。如果需要完整的百道练习题清单,请进一步说明需求! --- ### 示例代码片段 以下是一个简单例子——判断水仙花数: ```csharp static void Main(string[] args) { int a = 100; while (a <= 999) { int q = (int)(a / 100 % 10); int c = (int)(a / 10 % 10); int d = (int)(a % 10); if (q * q * q + c * c * c + d * d * d == a) Console.WriteLine($"这是一个水仙花数为: {a}"); a++; } } ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值