Java并发一直都是开发中比较难也比较有挑战性的技术,对于很多新手来说是很容易掉进这个并发陷阱的,其中尤以共享变量最具代表性,其实关于讲这个知识点网上也不少,但大象想讲讲自己对这个概念的理解。共享变量比较典型的就是指类的成员变量,在类中定义了很多方法对成员变量的使用,如果是单实例,当有多个线程同时来调用这些方法,方法又没加控制,那么这些方法对成员变量的操作就会使得该成员变量的值变得不准确了。

大象用一个最典型的i++例子来说明:

publicclassTest {

private int i= 0;

private finalCountDownLatchmainLatch=newCountDownLatch(1);

public voidadd(){i++;

}

privateclassWorkextendsThread{

privateCountDownLatchthreadLatch;publicWork(CountDownLatch latch){

threadLatch=

latch;

}

@Override

publicvoidrun() {

try{

mainLatch.await();

}catch(InterruptedException e) {

e.printStackTrace();}

for(intj

= 0; j < 1000; j++) {

add();

}

threadLatch.countDown();

}

}

publicstaticvoidmain(String[] args)throwsInterruptedException {

for(intk

= 0; k < 10; k++){

Test test =newTest();

CountDownLatch threadLatch =newCountDownLatch(10);

for(inti

= 0; i < 10; i++) {

test.newWork(threadLatch).start();

}

test.mainLatch.countDown();

threadLatch.await();

System.out.println(test.i);

}

}}

java.util.concurrent.CountDownLatch是JDK5.0提供的关于并发的一个新API,它的作用就像一个门闩或是闸门那样。上面这段代码一共执行10次,每次启动10个线程同时执行。mainLatch.await()相当于门闩挡着线程,让准备好的线程处于等待状态,当所有的线程都准备好时再调用mainLatch.countDown()方法,打开门闩让线程同时执行。我在这里用这个类的原因,是想让我创建的10个线程都准备好后再一起并发执行,这样才能很明显的看出add方法里面的i++效果。如果不引入CountDownLatch,只执行test.newWork(threadLatch).start(),则获得的结果可能看不出来线程竞争共享变量产生的错误情况。threadLatch这个CountDownLatch的作用是让10个线程都执行完run方法的for循环后通知主线程的threadLatch.await()停止等待打印出当前i的值。这段代码我加了-server参数运行了多次,每次结果都不一样,我取了几个比较明显的结果。当然,你也可以多运行几次看看效果。

共享变量i没做任何同步操作,当有多个线程都要读取并修改它时,问题就产生了。正确的结果应该是10000,但是我们看到了,不是每次结果都是10000。我这段代码最初的版本不是这样的,因为现在的CPU哪怕是家用级PC的CPU核心频率都非常高,所以完全看不出效果,在和一个朋友的讨论中,他给出了修改的建议,最后改为上面的代码,在这里谢谢Sunny君。run方法中的循环次数越大,i的并发问题就越明显,大家可以动手试下。对于上图的运行结果,和硬件平台有关,也和-server参数有关。有同学会有疑问了,既然共享变量没加同步处理,那为什么还是会出现10000的结果呢?关于这点我想这可能是JVM优化的结果,对于JVM(HotSpot)大象还没有很深入的研究,不敢随便下结论,请知道的朋友帮忙解答一下。

在Java中,线程是怎么操作共享变量的呢?我们都知道,Java代码在编译后会变成字节码,然后在JVM里面运行,而像实例域(i)这样的变量是存储在堆内存(Heap

Memory)中的,堆内存是内存中的一块区域。线程的执行其实说到底就是CPU的执行,当今的CPU(Intel)基本上都是多核的,因此多线程都是由多核CPU来处理,并且都有L1、L2或L3等CPU缓存,CPU为了提高处理速度,在执行的时候,会从内存中把数据读到缓存后再操作,而每个线程执行add方法操作i++的过程是这样的:

1、线程从堆内存中读取i的值,将它复制到缓存中

2、在缓存中执行i++操作,并将结果赋给变量i

3、再用缓存中的值刷新堆内存中的变量i的值

我上面写的这三步并不是严格按照JVM及CPU指令的步骤来的,但过程就是这么一回事,方便大家理解。通过上面这个过程我们可以看出问题了,如果有多个线程同时要修改i,那么都需要先读取堆内存中的变量i值,然后把它复制到缓存后执行i++操作,再将结果写回到堆内存的变量i中。这个执行的时间非常短,可能只有零点几纳秒(主要还是跟硬件平台有关),但还是出现了错误。产生这种错误的原因是共享变量的可见性,线程1在读取变量i的值的时候,线程2正在更新变量i的值,而线程1这时看不到线程2修改的值。这种现象就是常说的共享变量可见性。

下图是线程执行的抽象图,也可以说是Java内存模型的抽象示意图,可能不严谨,但大意是这样的。

现在选用开发框架一般都会选择Spring,或是类似Spring这样的东西,而代码中经常用到的依赖注入的Bean如果没做处理一般都会是单例模式。试想一下,按下面这个方式引用Service或其它类似的Bean,在UserService中又不小心用到了共享变量,同时没有处理它的共享可见性,即同步,那将会产生意想不到的结果。不光Service是单例的,Spring MVC中的Controller也是单例的,所以编写代码的时候一定要注意共享变量的问题。

@Autowired

privateUserServiceuserService;

所以我们要尽可能的不使用共享变量,避开它,因为处理好共享变量可见性不是一个很简单的问题。如果有非用不可的理由,请使用java.util.concurrent.atomic包下面的原子类来代替常用变量类型。比如用AtomicInteger代替int,AtomicLong代替long等等,具体可以参考API文档。如果需求比这更复杂,那还得想其它解决办法。

以上是大象关于共享变量的一些浅薄见解,有什么不对的,还请各位指出来。本文为菠萝大象原创,如要转载请注明出处。

posted on 2014-06-10 16:09 菠萝大象 阅读(8080) 评论(5)  编辑  收藏 所属分类: Concurrency

java 变量共享_浅谈Java共享变量相关推荐

  1. java 变量初始化_浅谈Java变量的初始化顺序详解

    规则1(无继承情况下):对于静态变量.静态初始化块.变量.初始化块.构造器,它们的初始化顺序依次是 (静态变量.静态初始化块)>(变量.初始化块)>构造器 证明代码: public cla ...

  2. java 多线程同步_浅谈Java多线程(状态、同步等)

    Java多线程是Java程序员必须掌握的基本的知识点,这块知识点比较复杂,知识点也比较多,今天我们一一来聊下Java多线程,系统的整理下这部分内容. 一.Java中线程创建的三种方式: 1.通过继承T ...

  3. java对象头_浅谈java对象结构 对象头 Markword

    概述 对象实例由对象头.实例数据组成,其中对象头包括markword和类型指针,如果是数组,还包括数组长度; | 类型 | 32位JVM | 64位JVM| | ------ ---- | ----- ...

  4. java bitset用途_浅谈Java BitSet使用场景和代码示例

    搜索热词 @H_502_0@一.什么是BitSet? @H_502_0@ 注:以下内容来自JDK API: @H_502_0@ BitSet类实现了一个按需增长的位向量.位Set的每一个组件都有一个b ...

  5. java手动回收_浅谈java是如何做资源回收补救的

    学习java的过程,我们经常谈论一个对象的回收,尤其是资源类型,如果没有显示的关闭,对象就被回收了,说明出现了资源泄漏.java本身为了防止这种情况,做了一些担保的方式,确保可以让未关闭的资源合理回收 ...

  6. java同名函数_浅谈Java 继承接口同名函数问题

    在Java中如果一个类同时继承接口A与B,并且这两个接口中具有同名方法,会怎么样? 动手做实验: interface A{ void fun(); } interface B{ void fun(); ...

  7. java list翻转_浅谈Java数据结构中的常见问题

    1.常用数据结构 数据结构是指相互之间存在着一种或多种关系的数据元素的集合和该集合中数据元素间的关系组成.常用的数据有:数组.栈.队列.链表.树.图.堆.散列表. 1)数组:在内存中连续存储多个元素的 ...

  8. java并发计数器_浅谈java并发之计数器CountDownLatch

    CountDownLatch简介 CountDownLatch顾名思义,count + down + latch = 计数 + 减 + 门闩(这么拆分也是便于记忆=_=) 可以理解这个东西就是个计数器 ...

  9. java 匿名类_浅谈Java的匿名类

    在实际的项目中看到一个很奇怪的现象,Java可以直接new一个接口,然后在new里面粗暴的加入实现代码.就像下面这样.那么问题来了,new出来的对象没有实际的类作为载体,这不是很奇怪吗? 思考以下代码 ...

最新文章

  1. Jquery知识小点备注
  2. 检测跟踪 DeepSOCIAL:基于YOLOv4的人群距离监测 集检测、跟踪以及逆透视映射一体的系统
  3. mysql 将三个月的数据导到历史表_迁移数据到历史表,减少业务表中数据压力 Mysql...
  4. Hi3516A开发-- 常见问题FAQs
  5. vs2010 插件不显示的问题处理。
  6. HDU-Yuna's confusion 树状数组 Or Multiset
  7. 动态规划-01背包问题详解
  8. 扫地机器人什么牌子好?2021最新扫地机器人排行榜
  9. [高光谱] Hyperspectral-Classification Pytorch 数据集的读取、划分、加载
  10. 7-9 字符串字母大小写转换 (15 point(s))
  11. 打码兔官网 验证码识别 远程答题服务 代答平台 验证码识别软件下载
  12. 破解版软件-20131014更新
  13. 荣耀pro无线路由器配置成无线交换机
  14. 新成员入群监控自动发送邮件效果如何实现?
  15. 多旋翼无人机ROSC++开发例程(四):基于Prometheus开源项目与Casadi开源优化求解器的模型预测控制简单应用例程
  16. ffmpeg一些基本用法
  17. 基于 FPGA 的数字表示
  18. 祝贺 弓叶科技总经理莫卓亚荣获“松山湖奋斗之星”
  19. 人工蜂群算法的java代码_求人工蜂群算法的c程序源代码``````谢谢各位大神了``````...
  20. ambari中zookeeper报错,Connection failed: Expected response imok, Actual response to...

热门文章

  1. The Netron Project For vb.net
  2. layui导航栏鼠标经过青色条块怎么移到顶部?
  3. WINVER not defined. Defaulting to 0x0600 (Windows Vista)
  4. nbu进程异常,备份失败
  5. 数据结构与算法----个人小结
  6. 智能体重秤解决方案开发
  7. project 2:属相与星座(Java(case的运用)的嵌套)
  8. C++网络编程学习:网络数据报文的收发
  9. iptables防火墙和SNET和DNET
  10. Mac环境下开发自有的可Remount的X86安卓镜像