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

要想理解数据同步的基本原理,首先就要明白,为什么我们要数据同步?

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的方式来处理。

我们上面的同步问题是发生在展示随机数字与字母的显示组件,现在我们继续将功能完善:玩家可以输入所显示的字母,并且正确就会得分。

标签: java 线程

synchronized和volatile的使用的更多相关文章

  1. Java 线程 — synchronized、volatile、锁

    线程同步基础 synchronized 和volatile是Java线程同步的基础. synchronized 将临界区的内容上锁,同一时刻只有一个进程能访问该临界区代码 使用的是内置锁,锁一个时刻只 ...

  2. java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析

    java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java ...

  3. java多线程之内存可见性-synchronized、volatile

    1.JMM:Java Memory Model(Java内存模型) 关于synchronized的两条规定: 1.线程解锁前,必须把共享变量的最新值刷新到主内存中 2.线程加锁时,将清空工作内存中共享 ...

  4. synchronized和volatile比较

    synchronized和volatile比较 volatile不需要加锁,比synchronized更轻量级,不会阻塞线程 从内存可见性角度讲,volatile读相当于加锁,volatile写相当于 ...

  5. java并发编程(2) --Synchronized与Volatile区别

    Synchronized 在多线程并发中synchronized一直是元老级别的角色.利用synchronized来实现同步具体有一下三种表现形式: 对于普通的同步方法,锁是当前实例对象. 对于静态同 ...

  6. synchronized与volatile的区别及各自的作用、原理(学习记录)

    synchronized与volatile的区别,它们的作用及原理? 说到两者的区别,先要了解锁提供的两种特性:互斥(mutual exclusion) 和可见性(visibility). 互斥:即一 ...

  7. synchronized和lock以及synchronized和volatile的区别

    synchronized和volatile区别synochronizd和volatile关键字区别: 1. volatile关键字解决的是变量在多个线程之间的可见性:而sychronized关键字解决 ...

  8. 对象回收过程?线程池执行过程? map原理?集合类关系?synchronized 和 volatile ? 同一个类的方法事务传播控制还有作用吗?java 锁

    1.  对象回收过程? 可达性分析算法: 如果一个对象从 GC Roots 不可达时,则证明此对象不可用. 通过一系列称为GC ROOTS的对象作为起点,从这些起点往下搜索,搜索走过的路径 称为引用链 ...

  9. Java多线程之内存可见性和原子性:Synchronized和Volatile的比较

    Java多线程之内存可见性和原子性:Synchronized和Volatile的比较     [尊重原创,转载请注明出处]http://blog.csdn.net/guyuealian/article ...

随机推荐

  1. HDU 1080 Human Gene Functions--DP--(变形最长公共子)

    意甲冠军:该基因序列的两端相匹配,四种不同的核苷酸TCGA有不同的分值匹配.例如T-G比分是-2,它也可以被加入到空格,空洞格并且还具有一个相应的核苷酸匹配分值,求最大比分 分析: 在空气中的困难格的 ...

  2. jquery_mobile.js+html5+css3打造手机平板等各种效果

    http://www.w3school.com.cn/jquerymobile/jquerymobile_events_orientation.asp

  3. 去掉UITableView HeaderView或FooterView随tableView 移动的黏性

    去掉UITableView HeaderView或FooterView随tableView 移动的黏性(sticky) 控制器中实现以下方法即可: - (void)scrollViewDidScrol ...

  4. 分布式基础学习(2)分布式计算系统(Map/Reduce)

    二. 分布式计算(Map/Reduce) 分 布式式计算,同样是一个宽泛的概念,在这里,它狭义的指代,按Google Map/Reduce框架所设计的分布式框架.在Hadoop中,分布式文件 系统,很 ...

  5. 关于ios的autoLayout的一些简单介绍以及使用方法

    一.autoLayout的用途: 主要用于屏幕适配,尤其是出现了iphone6,plus之后. 二.怎么简单的用autoLayout呢? 点击左一,可以看到: 点击左二: 基本上要想autolayou ...

  6. JavaScript的基准测试

    JavaScript的基准测试 原文:Bulletproof JavaScript benchmarks 做JavaScript的基准测试并没有想的那么简单.即使不考虑浏览器差异所带来的影响,也有很多 ...

  7. JSP中获取jstl中的数据

    我们在编程JSP时,有时会须要訪问jstl中的数据,或者说是el表达式中的数据. 比方, <c:forEach    varStatus="data1" var=" ...

  8. 探秘IntelliJ IDEA 13测试版新功能——调试器显示本地变量

    IntelliJ IDEA在业界被公认为最好的Java开发平台之一,JetBrains公司将在12月正式发布IntelliJ IDEA 13版本. 现在,小编将和大家一起探秘密IntelliJ IDE ...

  9. Asp.Net Identity 深度解析 之 注册登录的扩展

    关于权限每个系统都有自己的解决方案,今天我们来讨论一下微软的权限框架Asp.Net Identity ,介绍如下  http://www.asp.net/identity 这里不在赘余. 很多人认为 ...

  10. 如何在在网页上显示pdf文档

    ------解决方案--------------------通过flash插件 ------解决方案--------------------RAD PDF Release 2.7 http://www ...