`
ftj20003
  • 浏览: 130464 次
  • 性别: Icon_minigender_1
  • 来自: ...
社区版块
存档分类
最新评论

多线程基础总结三--volatile

    博客分类:
  • Java
阅读更多
   前面的两篇总结简单的说明了同步的一些问题,在使用基础的同步机制中还有两个可以分享的技术:volatile关键字和ThreadLocal。合理的根据场景利用这些技术,可以有效的提高并发的性能,下面尝试结合自己的理解叙述这部分的内容,应该会有理解的偏差,我也会尽量的在完善自己理解的同时同步更新文章的错误。
   或许在知道synchronized配和对象内部锁的机制以后,可以提高写出正确同步的并发程序成功率,但是这时候会遇到另一个大问题:性能!是的,对于synchronized带来的可能庞大的性能成本,开发者们总结出不同的优秀的优化方案:常见的是锁的分解和锁持有时间的最小化。有效的降低锁持有的时间对竞争线程激烈的调用会大大的提高性能,所以不要轻易的在方法上声明synchronized,应该在需要保护的代码块上添加synchronized。另一个方案是拆分锁的竞争颗粒的大小,与其几百个线程竞争一个对象的锁,不如几个或者几十个线程竞争多个对象的锁,常见的应用是ConcurrentHashMap的实现,其内部有类似的锁对象数组维护每段表内的线程竞争,默认16个对象锁,当然提供参数可调。这对于存储了成千上万个实例的map性能提升不言而喻,线程的竞争被分散到多段的小竞争,再也不用全部的堆在门口傻等了。
   但是synchronized同步和类似的机制带来的性能成本,还是使得开发者不能不研究无锁和低成本的同步机制来保证并发的性能。volatile就是被认为“轻量级的synchronized”,但是使用其虽然可以简化同步的编码,并且运行开销相对于JVM没有优化的竞争线程同步低,但是滥用将不能保证程序的正确性。锁的两个特性是:互斥和可见。互斥保证了同时只有一个线程持有对象锁进行共享数据的操作,从而保证了数据操作的原子性,而可见则保证共享数据的修改在下一个线程获得锁后看到更新后的数据。volatile仅仅保证了无锁的可见性,但是不提供原子性操作的保证!这是因为volatile关键字作用的设计是JVM阻止volatile变量的值放入处理器的寄存器,在写入值以后会被从处理器的cache中flush掉,写到内存中去。这样读的时候限制处理器的cache是无效的,只能从内存读取值,保证了可见性。从这个实现可以看出volatile的使用场景:多线程大量的读取,极少量或者一次性的写入,并且还有其他限制。
   由于其无法保证“读-修改-写”这样操作的原子性(当然java.util.concurrent.atomic包内的实现满足这些操作,主要是通过CAS--比较交换的机制,后续会尝试写写。),所以像++,--,+=,-=这样的变量操作,即使声明volatile也不会保证正确性。围绕这个原理的主题,我们可以大致的整理一下volatile代替synchronized的条件:对变量的写操作不依赖自身的状态。所以除了刚刚介绍的操作外,例如:
private volatile boolean flag;
  if(!flag) {
    flag == true;
}

类似这样的操作也是违反volatile使用条件的,很可能造成程序的问题。所以使用volatile的简单场景是一次性的写入之后,大量线程的读取并且不再改变变量的值(如果这样的话,都不是并发了)。这个关键字的优势还是在于多线程的读取,既保证了读取的低开销(与单线程程序变量差不多),又能保证读到的是最新的值。所以利用这个优势我们可以结合synchronized使用实现低开销读写锁:
/**
 * User: yanxuxin
 * Date: Dec 12, 2009
 * Time: 8:28:29 PM
 */
public class AnotherSyncSample {
    private volatile int counter;

    public int getCounter() { 
	return counter; 
    }

    public synchronized void add() {
        counter++;
    }
}

这个简单的例子在读的方法上没有使用synchronized关键字,所以读的操作几乎没有等待;而由于写的操作是原子性的违反了使用条件,不能得到保证,所以使用synchronized同步得到写的正确性保证,这个模型在多读取少写入的实际场景中应该要比都用synchronized的性能有不小的提升。
   另外还有一个使用volatile的好处,得自于其原理:内部禁止改变两个volatile变量的赋值或者初始化顺序,并且严格限制volatile变量和其周围非volatile变量的赋值或者初始化顺序。
/**
 * User: yanxuxin
 * Date: Dec 12, 2009
 * Time: 8:34:07 PM
 */
public class VolatileTest {
    public static void main(String[] args) {
        final VolatileSample sample = new VolatileSample();

        new Thread(new Runnable(){
            public void run() {
                sample.finish();
            }
        }).start();

         new Thread(new Runnable(){
            public void run() {
                sample.doSomething();
            }
        }).start();
    }
}

class VolatileSample {
    private volatile boolean finished;
    private int lucky;

    public void doSomething() {
        if(finished) {
            System.out.println("lucky: " + lucky);
        }
    }

    public void finish() {
        lucky = 7;
        finished = true;
    }
}

这里首先线程A执行finish(),完成finished变量的赋值后,线程B进入方法doSomething()读到了finish的值为true,打印lucky的值,预想状态下为7,这样完美的执行结束了。但是,事实是如果finished变量不是声明了volatile的话,过程就有可能是这样的:线程A执行finish()先对finished赋值,与此同时线程B进入doSomething()得到finished的值为true,打印lucky的值为0,镜头切回线程A,接着给lucky赋值为7,可怜的是这个幸运数字不幸杯具了。因为这里发生了扯淡的事情:JVM或许为了优化执行把两者的赋值顺序调换了。这个结果在单线程的程序中简直绝对一定肯定就是不可能,遗憾的是多线程存在这个隐患。
    所以不说其它的知识,想用Java实现正确,高性能的并发程序是需要处处小心的。后面想说的ThreadLocal就是看惯了线程为了共享数据而屡屡发生惨剧后,想把数据与线程死死绑定不共享的另一个技术。当然还想尝试写写对atomic包的理解,对并发集合的理解,对线程池的理解。所有的这些基础有个清晰的认识,才能有自信写写正确的,性能稍好的并发程序。
6
0
分享到:
评论
发表评论

文章已被作者锁定,不允许评论。

相关推荐

    Java基础知识点总结.docx

    十二、 多线程★★★★ 39 为什么要使用多线程 39 创建线程和启动 39 线程的生命周期 44 线程管理 45 线程同步 49 线程通信 52 线程池 58 死锁 64 线程相关类 65 十三、 同步★★★★★ 67 十四、 Lock接口 70 十五...

    Java多线程与并发-原理

    代码块和静态方法同对象,类锁 对比 对象锁不同对象,类锁 对比 对象锁同对象,类锁 对比 对象锁代码实现SyncDemoSyncThread总结synchronized底层实现原理实现synchronized基础对象在内存中的布局对象头结构Monitor...

    深入理解java内存模型

    重排序对多线程的影响 顺序一致性 数据竞争与顺序一致性保证 顺序一致性内存模型 同步程序的顺序一致性效果 未同步程序的执行特性 VOLATILE volatile的特性 volatile写-读建立的happens before关系 volatile写-读的...

    深入理解Java内存模型.程晓明(带书签文字版).pdf

    重排序对多线程的影响 15 顺序一致性 19 数据竞争与顺序一致性保证 19 顺序一致性内存模型 19 同步程序的顺序一致性效果 22 未同步程序的执行特性 24 VOLATILE 28 volatile 的特性 28 volatile 写-读建立的 ...

    Java开发常见问题总结.docx

    一些关键且实用的Java开发技巧: 基础语法与规范: 始终使用public class并遵循驼峰命名法。 使用final关键字以提高性能和明确意图(不可变对象)。 明确初始化变量,避免...注意volatile关键字的使用,确保多线程

    java面试题,180多页,绝对良心制作,欢迎点评,涵盖各种知识点,排版优美,阅读舒心

    【多线程】Volatile的理解 82 【*多线程】synchronized底层如何实现的? 82 【多线程】Callable 和 Runnable接口 82 【多线程】Java四种线程池的创建方法 83 【多线程】线程池原理和运行机制 83 【多线程】线程池对...

    java8源码-highconcurrency:《实战Java高并发程序设计》葛一鸣、郭超着电子工业出版社2015年11月第一版书上部分代码

    我认为这本书是从Java多线程的基础迈向实际开发架构的指导 第一章走入并行世界: 介绍了并发的基础理论:同步/异步,并发/并行,临界区,阻塞/非阻塞,死锁/饥饿/活锁,并发级别,定律,JMM中的原子性,可见性,有序...

    宋劲彬的嵌入式C语言一站式编程

    4.5. sig_atomic_t类型与volatile限定符 4.6. 竞态条件与sigsuspend函数 4.7. 关于SIGCHLD信号 34. 终端、作业控制与守护进程 1. 终端 1.1. 终端的基本概念 1.2. 终端登录过程 1.3. 网络登录过程 2. 作业控制 2.1. ...

    Java 虚拟机面试题全面解析(干货)

    由于Java虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式实现的。为了线 程切换后能恢复到正确的执行位置,每条线程都需要一个独立的程序计数器,各线程之间的计 数器互不影响,独立存储。 1.如果线程正在...

    Java虚拟机

    第2版在第1版的基础上做了很大的改进:根据最新的JDK1.7对全书内容进行了全面的升级和补充;增加了大量处理各种常见JVM问题的技巧和最佳实践;增加了若干与生产环境相结合的实战案例;对第1版中的错误和不足之处的...

    sesvc.exe 阿萨德

    分享到:更多0 前言 Map 这样的 Key Value 在软件开发中是非常经典的结构,常用于在内存中存放数据。 本篇主要想讨论 ConcurrentHashMap 这样一个并发容器,在正式开始之前我觉得有必要谈谈 HashMap,没有它就不会...

Global site tag (gtag.js) - Google Analytics