Java并发统计变量值偏差原因及解决方案

1 问题描述

在一个项目中,需要对发送的请求结果进行统计,开发同事定义了两个全局共享变量CommonUtil.ReqFailNum和ReqNum,分别记录请求失败数和发送的请求数。并在每次发送请求之前都假定该请求会处理失败,先对其累加,直到成功收到200的返回码后,重新修正失败数量。

最后当应用处理请求处于较频繁的阶段时,出现了ReqFailNum最后减为负数的情况,一次正常请求完成时,

CommonUtil.ReqFailNum ++;和CommonUtil.ReqFailNum --应该是成对出现的,这个统计值不应该为负数的。

发送请求的代码如下:

private static boolean XMLPost(String content, String sendUrl) throws Exception{
  boolean bn = false;

  if ( null != content ) {
      //初始假设请求发送失败,等待正常返回200后再将失败记录数--
      CommonUtil.ReqFailNum ++;

      URL url =null;
      URLConnection con = null;
      OutputStreamWriter out = null;
      try {
        url = new URL(sendUrl);
        con = url.openConnection();
      }catch (MalformedURLException e1) {
        throw new ConnException("MalformedURLException");
      } catch (IOException e) {
        throw new ConnException("IOException");
      }
      con.setConnectTimeout(2000);
      con.setReadTimeout(2000);
      con.setDoOutput(true);
      con.setRequestProperty("Connection", "keep-alive");
      con.setRequestProperty("Pragma:", "no-cache");
      con.setRequestProperty("Cache-Control", "no-cache");
      con.setRequestProperty("Content-Type", "text/xml");

      try {
        out = new OutputStreamWriter(con.getOutputStream(), "UTF-8");
        out.write(content);
        out.flush();
        out.close();
      } catch (UnsupportedEncodingException e) {
        throw new ConnException("UnsupportedEncodingException");
      } catch (IOException e) {
        String exceptionStr = CommonUtil.stackTraceStr(e);
        throw new ConnException("IOException."+exceptionStr);
      }finally{
        try {
          if(out != null){
            out.close();
          }
        } catch (IOException e) {

          throw new ConnException("IOException...");
        }
      }

      String headline = con.getHeaderField(0);
      if (headline != null && headline.indexOf("200") > -1) {
        CommonUtil.ReqFailNum --;
        CommonUtil.ReqNum ++;
        bn = true;
        logger.info("sendUrl:: return 200 ok" );
      }
  }
  return bn;
}

2 错误原因分析  

  统计变量在并发环境下,开发人员却忽视了其安全问题。由于该方法在Action中调用,客户端的每个请求,都会调用该方法。而Web服务器处理客户端的请求时,对每个请求都创建了一个线程去处理。这段对统计变量操作的代码,曝露在多线程环境下,却没有任何同步处理,很容易导致统计数据的不一致问题。  

  在这个应用中,ReqFailNum++这个操作实际上应该是一个原子操作,它包含了对内存的三个动作“读-修改-写”,并且结果状态依赖于之前的状态。上述代码,在没有同步的情况下,当两个线程同时执行这行代码时,可能读到的是同一个值,同时+1 ,最终应该是两次累计操作,结果只累加了一次,由于丢失了一次递增操作,最终的统计值就偏差了1。

  由于++代码是方法最初的几行,线程同时执行++操作的概率较大,而CommonUtil.ReqFailNum --;是在请求成功处理完成后执行的,这段时间涉及到网络请求,处理时间不确定性较大,所以- -操作同时执行的概率也较低。最终ReqFailNum++丢失的次数会多于ReqFailNum--丢失的次数,从而导致这个共享变量ReqFailNum的值成了负数。

3 解决办法  

  1)使用锁,将ReqFailNum++或--的操作放在同步代码块中  

  2)由于是简单的统计变量,可以利用原子变量的特性,使用AtomicInteger或AtomicLong

结论:Web项目中,共享变量的线程安全性容易被忽视,加上数据不一致问题的出现具有偶发、不可预测等因素(本来想截个图的,但是应用目前并发量小,没有出现数据不一致的现象,这也是并发问题隐蔽而不易被发现的原因),为了防患于未然,在项目伊始就应该分析并发因素,让开发人员关注可变状态的线程安全性问题,是非常必要的。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • 关于Java8 parallelStream并发安全的深入讲解

    背景 Java8的stream接口极大地减少了for循环写法的复杂性,stream提供了map/reduce/collect等一系列聚合接口,还支持并发操作:parallelStream. 在爬虫开发过程中,经常会遇到遍历一个很大的集合做重复的操作,这时候如果使用串行执行会相当耗时,因此一般会采用多线程来提速.Java8的paralleStream用fork/join框架提供了并发执行能力.但是如果使用不当,很容易陷入误区. Java8的paralleStream是线程安全的吗 一个简单的例子,

  • Java并发编程之volatile变量介绍

    volatile提供了弱同步机制,用来确保将变量更新通知到其它线程.volatile变量不会被缓存在寄存器中或者对其它处理器不可见的地方,因此在读取volatile变量时总会返回最新写入的值.可以想象成如下语义,然而volatile是更轻量级的同步机制.volatile只能确保可见性,但不能保证原子性.也就是说不能在复合操作用volatile变量,比如i++. 复制代码 代码如下: public synchronized void setValue(int value){ this.value

  • 详解java解决分布式环境中高并发环境下数据插入重复问题

    java 解决分布式环境中 高并发环境下数据插入重复问题 前言 原因:服务器同时接受到的重复请求 现象:数据重复插入 / 修改操作 解决方案 : 分布式锁 对请求报文生成 摘要信息 + redis 实现分布式锁 工具类 分布式锁的应用 package com.nursling.web.filter.context; import com.nursling.nosql.redis.RedisUtil; import com.nursling.sign.SignType; import com.nu

  • Java并发编程示例(九):本地线程变量的使用

    共享数据是并发程序最关键的特性之一.对于无论是继承Thread类的对象,还是实现Runnable接口的对象,这都是一个非常周重要的方面. 如果创建了一个实现Runnable接口的类的对象,并使用该对象启动了一系列的线程,则所有这些线程共享相同的属性.换句话说,如果一个线程修改了一个属性,则其余所有线程都会受此改变的影响. 有时,我们更希望能在线程内单独使用,而不和其他使用同一对象启动的线程共享.Java并发接口提供了一种很清晰的机制来满足此需求,该机制称为本地线程变量.该机制的性能也非常可观.

  • 实例讲解Java并发编程之变量

    编写线程安全需要关心的: 1.共享的变量 2.可变的变量 共享意味着多个线程可以同时访问,可变意味着其值在生命周期可以改变. 例如以下count 变量: 复制代码 代码如下: //线程不安全的类 public class UnsafeCount {     private int count = 0;    //该变量是共享的     public void increase() {    //这里没有同步机制,多个线程可以同时访问         count++;    //该变量是可变的  

  • Java并发编程之原子变量与非阻塞同步机制

    1.非阻塞算法 非阻塞算法属于并发算法,它们可以安全地派生它们的线程,不通过锁定派生,而是通过低级的原子性的硬件原生形式 -- 例如比较和交换.非阻塞算法的设计与实现极为困难,但是它们能够提供更好的吞吐率,对生存问题(例如死锁和优先级反转)也能提供更好的防御.使用底层的原子化机器指令取代锁,比如比较并交换(CAS,compare-and-swap). 2.悲观技术 独占锁是一种悲观的技术.它假设最坏的情况发生(如果不加锁,其它线程会破坏对象状态),即使没有发生最坏的情况,仍然用锁保护对象状态.

  • Java常见面试题之多线程和高并发详解

    volatile 对 volatile的理解 volatile 是一种轻量级的同步机制. 保证数据可见性 不保证原子性 禁止指令重排序 JMM JMM(Java 内存模型)是一种抽象的概念,描述了一组规则或规范,定义了程序中各个变量的访问方式. JVM运行程序的实体是线程,每个线程创建时 JVM 都会为其创建一个工作内存,是线程的私有数据区域.JMM中规定所有变量都存储在主内存,主内存是共享内存.线程对变量的操作在工作内存中进行,首先将变量从主内存拷贝到工作内存,操作完成后写会主内存.不同线程间

  • java 并发编程之共享变量的实现方法

    可见性 如果一个线程对共享变量值的修改, 能够及时的被其他线程看到, 叫做共享变量的可见性. Java 虚拟机规范试图定义一种 Java 内存模型 (JMM), 来屏蔽掉各种硬件和操作系统的内存访问差异, 让 Java 程序在各种平台上都能达到一致的内存访问效果. 简单来说, 由于 CPU 执行指令的速度是很快的, 但是内存访问的速度就慢了很多, 相差的不是一个数量级, 所以搞处理器的那群大佬们又在 CPU 里加了好几层高速缓存. 在 Java 内存模型里, 对上述的优化又进行了一波抽象. JM

  • Java并发统计变量值偏差原因及解决方案

    1 问题描述 在一个项目中,需要对发送的请求结果进行统计,开发同事定义了两个全局共享变量CommonUtil.ReqFailNum和ReqNum,分别记录请求失败数和发送的请求数.并在每次发送请求之前都假定该请求会处理失败,先对其累加,直到成功收到200的返回码后,重新修正失败数量. 最后当应用处理请求处于较频繁的阶段时,出现了ReqFailNum最后减为负数的情况,一次正常请求完成时, CommonUtil.ReqFailNum ++;和CommonUtil.ReqFailNum --应该是成

  • Java修改Integer变量值遇到的问题及解决

    目录 Java 修改Integer变量值 下面我尝试了两种方法去改变Integer的整型值 看看源码 Integer值比较需要注意的问题 原因 解决办法 Java 修改Integer变量值 对于Integer变量来说,比较变量值常见情形如下: Integer a = 1000; Integer b = 1000; Integer c = 100; Integer d = 100; System.out.println(a == b); System.out.println(c == d); "=

  • JavaScript交换两个变量值的七种解决方案

    前言 这篇文章总结了七种办法来交换a和b的变量值 var a = 123; var b = 456; 交换变量值方案一 最最最简单的办法就是使用一个临时变量了,不过使用临时变量的方法实在是太low了 var t; t = a; a = b; b = t; 首先把a的值存储到临时变量中,然后b赋值给a,最后拿出临时变量中的a值赋给b,这个办法是最基本的了 交换变量值方案二 下面的方案都不会有临时变量,我总结了一下,其实不使用临时变量的思路都是让其中一个变量变成一个a和b都有关系的值,这样可以先改变

  • Java并发中的ABA问题学习与解决方案

    目录 1.简介 2.Compare and swap 3. ABA问题 3.1 ABA问题的实际场景:账户余额修改 3.2 账户余额修改时产生的问题 4.银行取款问题代码演示 5.值类型与引用类型的场景 6. 解决方法 7. Java中的解决方法 8. 总结 1.简介 我们将了解在并发编程中的ABA问题.同时学习引起该问题的根因及问题解决办法. 2.Compare and swap 为了理解根本原因,首先回顾一下Compare and swap的概念.Compare and Swap (CAS)

  • java file.renameTo返回false的原因及解决方案

    java file.renameTo返回false原因 需要对文件夹下的文件重命名,发现返回false了,先用main方法测试,发现没问题,如下 public static void main(String[] args) throws IOException { File file1 = new File("D:\\aabb.xml"); File file2 = new File("D:\\ccdd.xml"); boolean b = file1.rename

  • Java反射如何修改private final成员变量值

    大家都知道使用java反射可以在运行时动态改变对象的行为,甚至是private final的成员变量,但并不是所有情况下,都可以修改成员变量.今天就举几个小例子说明. 基本数据类型 /** * @author Cool-Coding 2018/5/15 */ public class ReflectionUsage {private final int age=18; public int getAge(){ return age; } } 测试代码: import java.lang.refl

  • Java 反射修改类的常量值、静态变量值、属性值实例详解

    前言 有的时候,我们需要修改一个变量的值,但变量也许存在于 Jar 包中或其他位置,导致我们不能从代码层面进行修改,于是我们就用到了下面的场景,通过反射来进行修改变量的值. 定义一个实体类 class Bean{ private static final Integer INT_VALUE = 100; } 利用反射修改私有静态常量方法 System.out.println(Bean.INT_VALUE); Field field = Bean.class.getField("INT_VALUE

  • 利用反射获取Java类中的静态变量名及变量值的简单实例

    JAVA可以通过反射获取成员变量和静态变量的名称,局部变量就不太可能拿到了. public class Test { public static void main(String[] args) throws Exception { // TODO Auto-generated method stub //获取所有变量的值 Class clazz = Class.forName("com.qianmingxs.ScoreTable"); Field[] fields = clazz.g

  • Java 不使用第三方变量交换两个变量值的四种方法详解

    目录 变量本身交换数值 算术运算 指针地址操作 位运算 简单总结 哈喽,大家好,我是阿Q.前几天有个小伙伴去面试,被面试官的一个问题劝退了:请说出几种不使用第三方变量交换两个变量值的方法. 问题有点绕,好不容易缕清了面试官的问题,却发现答不上来.一时间尴尬无比,只能硬着头皮说不会. 遇到交换变量值的问题,通常我们的做法是:定义一个新的变量,借助它完成交换. 代码如下: t = a; a = b; b = t; 但问题的重点是"不使用第三方变量",那就变得"可爱"起来

  • Java 并发编程:volatile的使用及其原理解析

    Java并发编程系列[未完]: •Java 并发编程:核心理论 •Java并发编程:Synchronized及其实现原理 •Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) •Java 并发编程:线程间的协作(wait/notify/sleep/yield/join) •Java 并发编程:volatile的使用及其原理 一.volatile的作用 在<Java并发编程:核心理论>一文中,我们已经提到过可见性.有序性及原子性问题,通常情况下我们可以通过Synchroniz

随机推荐