synchronized和volatile的使用
现在开始进入线程编程中最重要的话题---数据同步,它是线程编程的核心,也是难点,就算我们理解了数据同步的基本原理,但是我们也无法保证能够写出正确的同步代码,但基本原理是必须掌握的。
要想理解数据同步的基本原理,首先就要明白,为什么我们要数据同步?

public class CharacterDisplayCanvas extends JComponent implements
CharacterListener {
protected FontMetrics fm;
protected char[] tmpChar = new char[1];
protected int fontHeight; public CharacterDisplayCanvas() {
setFont(new Font("Monospaced", Font.BOLD, 18));
fm = Toolkit.getDefaultToolkit().getFontMetrics(getFont());
fontHeight = fm.getHeight();
} public CharacterDisplayCanvas(CharacterSource cs) {
this();
setCharacterSource(cs);
} public void setCharacterSource(CharacterSource cs) {
cs.addCharacterListener(this);
} public synchronized void newCharacter(CharacterEvent ce) {
tmpChar[0] = (char) ce.character;
repaint();
} public Dimension preferredSize() {
return new Dimension(fm.getMaxAscent() + 10, fm.getMaxAdvance() + 10);
} protected synchronized void paintComponent(Graphics gc) {
Dimension d = getSize();
gc.clearRect(0, 0, d.width, d.height);
if (tmpChar[0] == 0) {
return;
}
int charWidth = fm.charWidth((int) tmpChar[0]);
gc.drawChars(tmpChar, 0, 1, (d.width - charWidth) / 2, fontHeight);
}
}

仔细查看上面的代码,我们就会发现,有两个方法的前面多了一个新的关键字:synchronized。让我们看看这两个方法为什么要添加这个关键字。
newCharacter()
用于显示新字母,而paintComponent()负责调整和重画canvas。这两个方法存在着race
condition,也就是竞争,因为它们访问的是同一份数据,最重要的是它们是由不同的线程所调用的,这就导致我们无法保证它们的调用是按照正确的顺序
来进行,可能在newCharacter()方法未被调用前paintComponent()方法就已经重新绘制canvas。
之所以产生竞争,除了这两个方法访问的是同一份数据之外,还和它们是非automic有关。我们在初中的时候都学过,原子曾经被认为是最
小单元,不可分的,哪怕现在已经证明这是不正确的,但原子不可分的概念在计算机这里保留了下来。
一个程序如果被认为是automic,那么就表示它是无法被中断的,不会有中间状态。使用synchronized,就能保证该方法无法被中断,那么其他
线程就无法在该方法没有完成前调用它。
结合对象锁的知识,我们可以简单的讲解一下synchronized的原理:一个线程如果想要调用另一个线程的synchronized方法,而且该方法
正在被其他线程调用,那么这个线程就必须等待,等待其他线程释放该方法所在的对象的锁,然后获得该锁执行该方法。锁机制能够确保同一时间只有一个线程能够
调用该方法,也就能保证只有一个线程能够访问数据。
还记得我们之前通过使用标记来结束线程的时候,将该标记用volatile修饰?如果我们不用volatile,又能使用什么方法呢?
如果单单只是上面的知识,我们可能会想到利用synchronized来同步run()和setDone(),因为就是这两个方法在竞争done这个数
据。但是这样存在很大的问题:run()会在done没有被设置true前永远不会结束,但是done标记却要等到run()方法结束后才能由
setDone()方法进行设置。
这就是一个死锁,永远解不开的锁。
产生死锁的原因有很多,像是上面这种情况就是一个典型的代表,主要原因就是run()方法的scope(范围)太大。所谓的scope,指的是获取锁到释
放锁的时间,而run()方法的scope是一个循环,除非done设置为true。这种需要依赖其他线程的方法来结束执行的方法,如果将整个方法设置为
同步,就会出现死锁。
所以,最好的方法就是将scope缩小。
我们可以不用对整个方法进行同步,而是对需要访问的数据进行同步,也就是对done使用volatile。
要想理解volatile的工作原理,我们必须清楚变量的加载机制。java的内存模型允许线程能够在local
memory中持有变量的值,所以这也就导致某个线程改变该变量的值时,其他线程可能不会察觉到该变量的变化。这种情况只是一种可能,并不代表一定会出
现,但像是循环执行这种操作,就增加了这种可能。
所以,我们要做的事情其实很简单,就是让线程从同一个地方取出变量而不是自己维护一份。使用volatile,每次使用该变量都要从主存储器中读取,每次
改变该变量时,也要存入主存储器,而且加载和存储都是automic,无论是否是long或者double变量(这两种类型的存储是非automic
的)。
值得注意的,run()方法和setDone()方法本身就是automic,因为setDone()方法仅有一个存储操作,而run()方法也只有一个读取操作,其余部分根本就需要该值保持不变,也就是说,这两个方法其实本身就不存在竞争。
当然,如果还是坚持想要使用synchronized的话,倒是有个比较丑陋的方法:对done提供setter和getter,然后
synchronized这两个方法,因为取得同步化的锁代表所有暂时存储于寄存器的值都会被清空到主存储器中,这样run()方法中要想取得done就
必须等到setDone()方法设置完毕。
多么丑陋的实现啊!!就为了同步一个变量,结果我们就要平白对两个方法进行同步,增加无谓的线程开销!!但这也是没有办法的事,如果我们不知道还有volatile的话,没准还会为自己的小聪明而开心不已!!
这就是多线程编程的现实,如果我们无法知道还有更加优雅的实现,我们永远也只能写出这样的代码。
但让人更加困惑的是,volatile本身的存在现在也引起人们的关注:它到底有没有必要?
volatile是以moot point(未决点)来实现的:变量永远都从主存储器中读取,但这也只是JDK
1.2之前的情况,现在的虚拟机实现使得内存模式越来越复杂,而且也得到了极大的优化,并且这种趋势只会一直持续下去。也就是说,基于内存模式的
volatile可能会因为内存模式的不断优化而逐渐变得没有意义。
volatile的使用是有局限的,它仅仅解决因内存模式而引发的问题,而且只能用在对变量的automic操作上,也就是访问该变量
的方法只可以有单一的加载或者存储。但很多方法都是非automic,像是递增或者递减操作,就允许存在中间状态,因为它们本身就是载入,变更和存储的简
化而已,也就是所谓的syntactic sugar(语法糖)。
我们大概可以这样理解volatile的使用条件:强迫虚拟机不要临时复制变量,哪怕我们在许多情况下都不会使用它们。
volatile是否可以运用在数组上,让整个数组中的所有元素都被同步呢?凡是使用java的人都会对这样的幻想嗤之以鼻,因为实际
情况是只有数组的引用才会被同步,数组中的元素不会是volatile的,虚拟机还是可以将个别元素存储于local的寄存器中,没有任何方法可以指定数
组的元素应该以volatile的方式来处理。
我们上面的同步问题是发生在展示随机数字与字母的显示组件,现在我们继续将功能完善:玩家可以输入所显示的字母,并且正确就会得分。
synchronized和volatile的使用的更多相关文章
- Java 线程 — synchronized、volatile、锁
线程同步基础 synchronized 和volatile是Java线程同步的基础. synchronized 将临界区的内容上锁,同一时刻只有一个进程能访问该临界区代码 使用的是内置锁,锁一个时刻只 ...
- java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析
java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java ...
- java多线程之内存可见性-synchronized、volatile
1.JMM:Java Memory Model(Java内存模型) 关于synchronized的两条规定: 1.线程解锁前,必须把共享变量的最新值刷新到主内存中 2.线程加锁时,将清空工作内存中共享 ...
- synchronized和volatile比较
synchronized和volatile比较 volatile不需要加锁,比synchronized更轻量级,不会阻塞线程 从内存可见性角度讲,volatile读相当于加锁,volatile写相当于 ...
- java并发编程(2) --Synchronized与Volatile区别
Synchronized 在多线程并发中synchronized一直是元老级别的角色.利用synchronized来实现同步具体有一下三种表现形式: 对于普通的同步方法,锁是当前实例对象. 对于静态同 ...
- synchronized与volatile的区别及各自的作用、原理(学习记录)
synchronized与volatile的区别,它们的作用及原理? 说到两者的区别,先要了解锁提供的两种特性:互斥(mutual exclusion) 和可见性(visibility). 互斥:即一 ...
- synchronized和lock以及synchronized和volatile的区别
synchronized和volatile区别synochronizd和volatile关键字区别: 1. volatile关键字解决的是变量在多个线程之间的可见性:而sychronized关键字解决 ...
- 对象回收过程?线程池执行过程? map原理?集合类关系?synchronized 和 volatile ? 同一个类的方法事务传播控制还有作用吗?java 锁
1. 对象回收过程? 可达性分析算法: 如果一个对象从 GC Roots 不可达时,则证明此对象不可用. 通过一系列称为GC ROOTS的对象作为起点,从这些起点往下搜索,搜索走过的路径 称为引用链 ...
- Java多线程之内存可见性和原子性:Synchronized和Volatile的比较
Java多线程之内存可见性和原子性:Synchronized和Volatile的比较 [尊重原创,转载请注明出处]http://blog.csdn.net/guyuealian/article ...
随机推荐
- REUSEADDR 选项
一般而言,对于处理2MSL状态的套接字(一般为服务端套接字)是不允许接受从同一客户端重新发起一个新的连接的,但是套接字编程系统接口允许应用程序通过设置一个REUSEADDR选项,使处于2MSL状态的套 ...
- UI 收集
semantic http://www.semantic-ui.com.cn/modules/reveal.html sbadmin http://startbootstrap.com/templat ...
- 步步详解近期大火的density_peak超赞聚类
近期忙着在公司捣腾基于SOA的应急框架,还是前两周才在微博上看见了density_peak,被圈内好些人转载. 由于这个算法的名字起的实在惹眼,都没好意思怎么把这个算法名字翻译成中文,当然更惹眼的是, ...
- SoC嵌入式软件架构设计II:否MMU的CPU虚拟内存管理的设计与实现方法
大多数的程序代码是必要的时,它可以被加载到内存中运行.手术后,可直接丢弃或覆盖其他代码.我们PC然在同一时间大量的应用,能够整个线性地址空间(除了部分留给操作系统或者预留它用),能够觉得每一个应用程序 ...
- 浅析 JavaScript 中的 函数 currying 柯里化
原文:浅析 JavaScript 中的 函数 currying 柯里化 何为Curry化/柯里化? curry化来源与数学家 Haskell Curry的名字 (编程语言 Haskell也是以他的名字 ...
- Spring Resource之ResourceLoader
ResourceLoader接口意味着任何实现的对象都能够返回Resource实例. public interface ResourceLoader { Resource getResource(St ...
- Linux下访问文件的基本模式
源址:http://blogread.cn/it/article/6523?f=wb 访问文件的操作主要是指读文件和写文件,下文简单说明内核中几种常见的访问文件的方式. 普通模式 读写系统调用的默认方 ...
- TODOList项目
[ 爱上Swift]十二期:TODOList项目 好久没有写Swift甚是想念,Swift,Xcode都比较稳定了写个程序熟悉一下,当然时间原因都是小Demo,废话不多说先上图. 下面是跑起来之后 ...
- 设置Cookie,登录记住用户登录信息,获取用户登录过得信息
function setCookie(name,value) { var Days = 30; var exp = new Date(); exp.setTime(exp.getTime() + Da ...
- CQRS架构如何实现高性能
CQRS架构如何实现高性能 CQRS架构简介 前不久,看到博客园一位园友写了一篇文章,其中的观点是,要想高性能,需要尽量:避开网络开销(IO),避开海量数据,避开资源争夺.对于这3点,我觉得很有道理. ...