Java多线程中变量的可见性
之所以写这篇博客, 是因为在csdn上看到一个帖子问的就是这个问题. 废话不多说, 我们先看看他的代码(为了减少代码量, 我将创建线程并启动的部分修改为使用方法引用).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
|
这是学习多线程非常典型的生产者消费者. 那么这段代码有问题吗? 为什么输出几行以后就不动了呢?是死锁了吗? 这里我们先复习一下死锁的定义和产生条件.
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
而出现死锁必然满足四个条件:
- 互斥条件.
- 请求和保持条件
- 不剥夺条件
- 环路等待条件
再看看这个程序, 第二条就不满足: 一个线程同一时间只会处于保持锁或者请求锁的状态, 根本就没有出现请求和保持同时出现的情况. 换(shuo)句(ju)话(ren)说(hua), 这里只有一个锁, 怎么可能发生死锁呢? 要证明这一点很简单, 只需要在两个线程的while和if之间加一句打印的语句就知道了. 比如create方法中:
1 2 3 4 5 6 7 8 9 10 11 |
|
在编译运行程序, 就会发现程序并没有死锁, 那么为什么程序就是不执行同步块中的程序呢? 仔细看一下刚才程序的输出就知道原因了
1 2 3 4 |
|
我们发现不管是create方法还是consume方法, 都不满足进入if语句的条件. 怎么会这样呢? create方法明明将created赋值为true了. 其实, 单独看create方法和consume方法是看不出问题的. 这两个方法很正确. 问题其实是出在多线程中变量的可见性上. 在《JAVA并发编程实践》(点击查看豆瓣评价)3.1节中说:
在没有同步的情况下, 编译器, 处理器, 运行时安排操作的执行顺序可能完全出人意料. 在没有进行适当同步的多线程程序中, 尝试推断那些"必然"发生在内存中的动作时, 你总是会判断错误.
换句话说, 即使create方法将created赋值为true, 如果没有适当的同步, 那么consume方法中看见的可能还是以前的false. 同样, create方法看到的也可能是以前的值. 结果, 两个方法就都无法进入自己的if语句块了.
更糟糕的是, 过期情况并不一定会马上发生, 也不一定会发生在所有的变量上, 当然也不会完全不出现. 所以就有可能被忽略.
volatile关键字是干什么的呢? 上面也说了, 过期情况只会发生在没有适当同步的多线程程序中. 说道同步, 首先想到的应该就是加锁了吧. 但是加锁的开销太大, 而且不合适的锁会导致基本上线性执行的多线程程序(就像这个例子中的程序). 那么有没有其他的方法呢? 那就得靠volatile了.
《JAVA并发编程实践》中是这样说的:
Java语言也提供了其他的选择, 及一种同步的弱形式: volatile变量. 它确保对一个变量的更新会以可预见的方式告知其他的线程.
…
所以, 读一个volatile类型的变量时, 总会返回由某一线程所写入的最新值.
也就是说, 我们只需要将create变量的声明前添加volatile关键字即可解决问题:
1 |
|
另外, 需要注意《JAVA并发编程实践》里面还说到:
当验证正确性必须推断可见性问题时, 应该避免使用volatile变量. 正确使用volatile变量的方式包括: 用于确保他们所引用的对象状态的可见性, 或者用于标示重要的生命事件(比如初始化或者关闭)的方法
也就是说, volatile虽好, 但不能到处随意的使用. 可能是因为volatile容易用错, 所以这个关键字比较低调, 很多地方都没有提过这个关键字. 简单的说, 因为volatile不能提供原子性, 所以使用volatile的变量的所有读取/修改必须是原子修改, 比如x++就不是, 因为是先读取又写入. 这里我们就不深入说了, 要了解更多信息可以翻翻《JAVA并发编程实践》或者看看这篇"文档"《Java 理论与实践: 正确使用 Volatile 变量》.
最后, 我们再说说如何使用同步块解决这个问题. 其实很简单, 只需要将同步块和if语句对调即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
分类: 编程语言 标签: java, 多线程
原创作者:BK.转载请注明出处. 原文地址: http://iamlbk.github.io/blog/20160109/java-thread-visibility/
2016年01月09日 6:52 pm
Java多线程中变量的可见性的更多相关文章
- java多线程中的三种特性
java多线程中的三种特性 原子性(Atomicity) 原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,既不被中断操作,要不执行完成,要不就不执行. 如果一个操作时原子性的,那么多线程并 ...
- Java多线程中的竞争条件、锁以及同步的概念
竞争条件 1.竞争条件: 在java多线程中,当两个或以上的线程对同一个数据进行操作的时候,可能会产生“竞争条件”的现象.这种现象产生的根本原因是因为多个线程在对同一个数据进行操作,此时对该数据的操作 ...
- java 多线程中的wait方法的详解
java多线程中的实现方式存在两种: 方式一:使用继承方式 例如: PersonTest extends Thread{ String name; public PersonTest(String n ...
- java多线程中并发集合和同步集合有哪些?区别是什么?
java多线程中并发集合和同步集合有哪些? hashmap 是非同步的,故在多线程中是线程不安全的,不过也可以使用 同步类来进行包装: 包装类Collections.synchronizedMap() ...
- java多线程中最佳的实践方案是什么?
java多线程中最佳的实践方案是什么? 给你的线程起个有意义的名字.这样可以方便找bug或追踪.OrderProcessor, QuoteProcessor or TradeProcessor 这种名 ...
- Java多线程中的常用方法
本文将带你讲诉Java多线程中的常用方法 Java多线程中的常用方法有如下几个 start,run,sleep,wait,notify,notifyAll,join,isAlive,current ...
- Java多线程中的死锁
Java多线程中的死锁 死锁产生的原因 线程死锁是指由两个以上的线程互相持有对方所需要的资源,导致线程处于等待状态,无法往前执行. 当线程进入对象的synchronized代码块时,便占有了资源,直到 ...
- Java多线程中提到的原子性和可见性、有序性
1.原子性(Atomicity) 原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,既不被中断操作,要不执行完成,要不就不执行. 如果一个操作时原子性的,那么多线程并发的情况下,就不会出 ...
- java多线程中 volatile与synchronized的区别-阿里面试
volatile 与 synchronized 的比较(阿里面试官问的问题) ①volatile轻量级,只能修饰变量.synchronized重量级,还可修饰方法 ②volatile只能保证数据的可见 ...
随机推荐
- Android 用ping的方法判断当前网络是否可用
判断网络的情况中,有个比较麻烦的情况就是连上了某个网络,但是那个网络无法上网 ,,, = = 想到了用ping指令来判断,经测试,可行~ ~ ~ private static final boolea ...
- C++11 tuple
tuple元组定义了一个有固定数目元素的容器,其中的每个元素类型都可以不相同,这与其他容器有着本质的区别.是对pair的泛化. 首先来介绍元组的创建和元组元素的访问.通过make_tuple()创建元 ...
- Python 的数据类型
列表 #coding=utf-8 students=["小明","小华","小李","小娟","小云" ...
- nginx配置学习文章
partOne 自我释义部分 我的是阿里云的ubuntu *******实际上感觉这里是基本配置,很用不到*********#定义其用户或用户组user www-data;#nginx的进程数,应当为 ...
- JS 实现 Tab标签切换功能
Tab标签切换 效果图: HTML部分: <div class="wrap"> <ul id="tag"> < ...
- 使用PHP CURL的POST数据
使用PHP CURL的POST数据 curl 是使用URL语法的传送文件工具,支持FTP.FTPS.HTTP HTPPS SCP SFTP TFTP TELNET DICT FILE和LDAP.cur ...
- eclipse - copy类的全名
由于多次操作,感觉eclipse应该提供这个功能,网上搜一下,发现需要安装插件. 下载地址为 http://www.jave.de/eclipse/copyfully/copyfully_1.2.0. ...
- 利用FTP将Linux文件备份到Windows
windows:Windows Server 2008 linux: CentOS release 5.5 (Final) 首先在windows上安装好FTP,本人使用的是Windows ...
- 利用 Makefile 写的小程序
1.建立一个工程 2.写一个进度条的程序(原理就是在同一位置重复打印某一个字符(变化),达到动态显示的效果) 所以说我们这里只用回车'\r',覆盖这一行以前的输出,重新向缓冲区写数据刷新缓冲区,就能达 ...
- 三维FEM的刚度矩阵数量级
刚刚在调试三维的FEM代码,随手把全局刚度矩阵在FULL的情况下保存到了dat文件里. 注意,这个仅仅是一个半径为十的球的刚度矩阵 居然3.6G!!吓坏了!!截个图,留个纪念.还是老老实实用spars ...