Java多线程 a++线程问题 找出减少的地方
发布日期:2021-06-30 13:12:05 浏览次数:3 分类:技术文章

本文共 7908 字,大约阅读时间需要 26 分钟。

文章目录

AtomicInteger原子类来统计相加减少的次数

如下的代码, 使用AtomicInteger原子类来统计相加减少的次数. 和发生错误的次数.

并且用布尔数组, 如果某个值, 相加了, 那么就设置成true.(boolean数组 , 里面的元素, 默认为false)

public class MultiThreadsError implements Runnable{
static MultiThreadsError instance = new MultiThreadsError(); int index = 0; //真正加的次数 static AtomicInteger realIndex = new AtomicInteger(); //发生错误的次数 AtomicInteger的作用是把i++的操作, 三步合为一步, 原理为 cas static AtomicInteger wrongIndex = new AtomicInteger(); //存储当前值的数组 final boolean[] marked = new boolean[1000000000]; public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(instance); Thread thread2 = new Thread(instance); thread1.start(); thread2.start(); //主线程 等待子线程 thread1.join(); thread2.join(); //打印结果 System.out.println("执行结果"+instance.index); System.out.println("真正运行的次数"+ realIndex.get()); System.out.println("发生错误的次数"+ wrongIndex.get()); } @Override public void run() {
for (int i = 0; i < 10000; i++) {
index++; //自增 realIndex.incrementAndGet(); if (marked[index]) {
//如果该值, 已经被加过了, 那么就会进入此代码块 //打印出线程错误的下标 System.out.println("发生了错误" + index); //相加错误的数量 wrongIndex.incrementAndGet(); } //把当前加完的值 ,设置为true marked[index] = true; } }}

之所以用如下的代码,进行出现错误的时候的打印, 是因为上一篇文章中, 已经提到, 在发生线程不安全问题的时候, 变量i的值, 还没来得及赋值给当前线程, 就去执行线程2的代码了. 这样就会导致i的值变少, 但是会布尔数组的索引是相同的, if的判断就是为true的.

执行程序, 可以看到执行结果与发生错误的次数相加并不是2万,
发生上面错误的原因是, 可能在执行57行代码的时候, 也会出现线程的不安全问题 . 一个线程执行完了i++操作, 执行到49行代码为false , 原本打算执行57行代码, 把标记位设置成true, 但此时发生了线程的切换. 第二个线程判断49行代码的时候的, 原本应该是true的, 但是却是false. 所以就导致了发生错误

synchronized 加锁进行统计

按照上面的分析, 把代码修改如下, 把可能出现线程切换的地方, 加上锁 .

但此时执行代码发现, 执行的结果明明是2万 , 没有出错的 , 但是统计发生错误却是479条.
假设两个线程都执行完上面的46 48行代码, 同时到50行代码去拿锁, 假设此时发生了冲突. 此时index为0 , 相加后变成了1 , 第一个线程把布尔数组的下标为1 的设置成了true, 但可能在线程2执行到51行代码的时候, 就把线程切换成了线程1, 此时线程1执行++操作, index就变成了2. 那么此时原本是想比较的是index为1 的时候, index 却变成了2.

CyclicBarrier 线程等待的工具类

这个时候, 就需要引入一个工具类, 这个工具类的作用是让线程在需要等待 的地方进行等待. 不想让线程等待的地方, 可以进行统一的执行.

这个工具类就是CyclicBarrier, 可以根据我们的需要, 在某个地方进行等待, 直到等待的资源都就绪了,再执行代码 .

引入CyclicBarrier 工具类后, 改造如下 . 在循环一开始和执行++操作的时候, 进行等待,

package com.thread.background;import java.util.concurrent.BrokenBarrierException;import java.util.concurrent.CyclicBarrier;import java.util.concurrent.atomic.AtomicInteger;/** * 类名称:MultiThreadsError * 类描述:  第一种:运行结果出错。 演示计数不准确(减少),找出具体出错的位置。 * * @author: https://javaweixin6.blog.csdn.net/ * 创建时间:2020/8/31 19:18 * Version 1.0 */public class MultiThreadsError implements Runnable{
static MultiThreadsError instance = new MultiThreadsError(); int index = 0; //真正加的次数 static AtomicInteger realIndex = new AtomicInteger(); //发生错误的次数 AtomicInteger的作用是把i++的操作, 三步合为一步, 原理为 cas static AtomicInteger wrongIndex = new AtomicInteger(); //CyclicBarrier构造方法, 传递的参数代表需要等待几个线程 static volatile CyclicBarrier cyclicBarrier1 = new CyclicBarrier(2); static volatile CyclicBarrier cyclicBarrier2 = new CyclicBarrier(2); //存储当前值的数组 final boolean[] marked = new boolean[1000000000]; public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(instance); Thread thread2 = new Thread(instance); thread1.start(); thread2.start(); //主线程 等待子线程 thread1.join(); thread2.join(); //打印结果 System.out.println("执行结果"+instance.index); System.out.println("真正运行的次数"+ realIndex.get()); System.out.println("发生错误的次数"+ wrongIndex.get()); } @Override public void run() {
for (int i = 0; i < 10000; i++) {
try {
//等待的重置 cyclicBarrier2.reset(); //等待的两个线程如果都到齐了, 那么就放行. cyclicBarrier1.await(); } catch (InterruptedException e) {
e.printStackTrace(); } catch (BrokenBarrierException e) {
e.printStackTrace(); } index++; //两个线程都去执行++操作的时候, 才去放行 try {
//等待的重置 cyclicBarrier1.reset(); cyclicBarrier2.await(); } catch (InterruptedException e) {
e.printStackTrace(); } catch (BrokenBarrierException e) {
e.printStackTrace(); } //自增 realIndex.incrementAndGet(); synchronized (instance) {
if (marked[index]) {
//如果该值, 已经被加过了, 那么就会进入此代码块 //打印出线程错误的下标 System.out.println("发生了错误" + index); //相加错误的数量 wrongIndex.incrementAndGet(); } //把当前加完的值 ,设置为true marked[index] = true; } } }}

可以看到统计的 发生错误的统计还是出错了. 但是可以发现的规律是统计的发生的错误, 都是偶数.

主要是因为synchronized的可见性, 让两个线程的结果可以互相的通知.

发生错误的逻辑修改

需要把上面的代码, 修改一处. 不仅仅是判断当前索引, 还得判断上一个索引是否为true.

通过debug断点可以看到. 布尔数组的规律基本上是前面一个是true, 后面一个是false.
把布尔数组的第一个元素设置成true, 用于防止第一次就发生了错误
此时控制台打印如下 , 可以看到统计没有出错了.

完整的代码如下:

package com.thread.background;import java.util.concurrent.BrokenBarrierException;import java.util.concurrent.CyclicBarrier;import java.util.concurrent.atomic.AtomicInteger;/** * 类名称:MultiThreadsError * 类描述:  第一种:运行结果出错。 演示计数不准确(减少),找出具体出错的位置。 * * @author: https://javaweixin6.blog.csdn.net/ * 创建时间:2020/8/31 19:18 * Version 1.0 */public class MultiThreadsError implements Runnable{
static MultiThreadsError instance = new MultiThreadsError(); int index = 0; //真正加的次数 static AtomicInteger realIndex = new AtomicInteger(); //发生错误的次数 AtomicInteger的作用是把i++的操作, 三步合为一步, 原理为 cas static AtomicInteger wrongIndex = new AtomicInteger(); //CyclicBarrier构造方法, 传递的参数代表需要等待几个线程 static volatile CyclicBarrier cyclicBarrier1 = new CyclicBarrier(2); static volatile CyclicBarrier cyclicBarrier2 = new CyclicBarrier(2); //存储当前值的数组 final boolean[] marked = new boolean[1000000000]; public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(instance); Thread thread2 = new Thread(instance); thread1.start(); thread2.start(); //主线程 等待子线程 thread1.join(); thread2.join(); //打印结果 System.out.println("执行结果"+instance.index); System.out.println("真正运行的次数"+ realIndex.get()); System.out.println("发生错误的次数"+ wrongIndex.get()); } @Override public void run() {
//把布尔数组的第一个元素设置成true, 用于防止第一次就发生了错误 marked[0] = true; for (int i = 0; i < 10000; i++) {
try {
//等待的重置 cyclicBarrier2.reset(); //等待的两个线程如果都到齐了, 那么就放行. cyclicBarrier1.await(); } catch (InterruptedException e) {
e.printStackTrace(); } catch (BrokenBarrierException e) {
e.printStackTrace(); } index++; //两个线程都去执行++操作的时候, 才去放行 try {
//等待的重置 cyclicBarrier1.reset(); cyclicBarrier2.await(); } catch (InterruptedException e) {
e.printStackTrace(); } catch (BrokenBarrierException e) {
e.printStackTrace(); } //自增 realIndex.incrementAndGet(); synchronized (instance) {
if (marked[index] && marked[index-1]) {
//如果该值, 已经被加过了, 那么就会进入此代码块 //打印出线程错误的下标 System.out.println("发生了错误" + index); //相加错误的数量 wrongIndex.incrementAndGet(); } //把当前加完的值 ,设置为true marked[index] = true; } } }}

转载地址:https://javaweixin6.blog.csdn.net/article/details/108328310 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Java多线程 演示死锁的发生
下一篇:Java多线程 结果错误:a++会让加的次数减少

发表评论

最新留言

第一次来,支持一个
[***.219.124.196]2024年04月20日 21时58分58秒