`
ftj20003
  • 浏览: 132379 次
  • 性别: 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
分享到:
评论
发表评论

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

相关推荐

    自己总结的多线程

    ### 多线程基础 #### 1. 多线程概念 多线程是指在一个程序中包含多个控制流,它们可以并发执行不同的任务。在Java中,多线程的实现通常借助于`Thread`类或实现`Runnable`接口。多线程能够提高CPU的利用率,改善应用...

    Java多线程知识点总结

    总之,掌握Java多线程的生命周期、创建、启动、同步以及线程池的使用是编写高效、稳定并发程序的基础。理解这些知识点对于解决并发编程中的问题,比如资源竞争、死锁、线程安全性等问题,至关重要。在实际开发中,...

    线程及线程应用总结

    - volatile关键字:保证多线程环境下变量的可见性和有序性。 - final关键字:确保初始化一次且不可变,有利于线程安全。 - ThreadLocal:为每个线程提供独立的变量副本,避免共享导致的冲突。 5. **文件名关联...

    Java 多线程学习总结归纳(附代码)

    以上内容涵盖了Java多线程的基础知识,包括创建、同步、终止、线程安全和并发控制等方面。通过实际的代码实践,可以深入理解并掌握这些概念,提升多线程编程的能力。同时,文档中的代码示例能帮助读者更好地理解和...

    多线程编程指南

    #### 三、多线程的支持与管理 1. **线程支持** - **线程包**:提供了一套标准的API用于创建和管理线程。 - **RunLoops**:管理线程的生命周期,控制线程的运行和暂停。 - **同步工具**:如锁、条件变量等,用于...

    Java多线程与线程安全实践-基于Http协议的断点续传

    总结起来,Java多线程和线程安全是构建高性能应用的基础,而在HTTP协议下实现断点续传则能显著提升大文件下载的体验。理解并熟练运用这些技术,可以让你在Java开发中游刃有余,提供更优质的服务。

    大学毕业设计Java多线程与线程安全实践-基于Http协议的断点续传.zip

    一、Java多线程基础 多线程是指在单个应用程序中同时执行多个线程的能力。在Java中,我们可以通过实现Runnable接口或继承Thread类来创建线程。通过多线程,可以将不同的任务分配到不同的线程中,从而实现任务的并行...

    Java 多线程与并发(1-26)-Java 并发 - 理论基础.pdf

    Java 多线程与并发理论基础 Java 多线程与并发是 Java 编程语言中的一种机制,用于提高程序的执行效率和响应速度。多线程的出现是为了解决 CPU、内存、I/O 设备速度差异的问题,通过分时复用 CPU、缓存和进程、...

    多线程起步,总结的,一些方法的使用,简单的列子,使用方法

    本文将深入探讨多线程的基础知识,包括它的概念、使用场景、核心方法以及通过简单示例来帮助初学者理解多线程的实践操作。 1. 多线程概念: 多线程是指在一个应用程序中同时执行多个独立的执行路径,每个路径称为一...

    多线程编程指南(苹果公司多线程编程指南)

    ### 多线程编程指南(苹果公司多线程编程指南) #### 重要概念与知识点 **一、关于多线程编程** ...- 总结全文,强调多线程编程的重要性和注意事项。 **推荐资源** - 推荐进一步学习多线程编程的相关资源和书籍。

    63-Java多线程知识点总结1

    Java多线程知识点总结主要讲解了Java中多线程编程的基础知识,包括线程的启动、volatile变量、多线程共享数据、wait、notify、notifyAll等。 线程的启动 在Java中,线程的启动可以通过start()方法来实现,start()...

    Java多线程的总结

    这篇总结将深入探讨Java多线程的基础概念、特性以及常见用法,旨在为初学者提供一个全面的学习指南。 一、线程的基本概念 在Java中,线程是程序执行的最小单位,每个线程都有自己的程序计数器、虚拟机栈、本地方法...

    Java多线程编程总结

    #### 一、Java多线程基础 在Java中,多线程编程是一项非常重要的技术,它能够有效地提高程序的执行效率,并行处理多个任务。多线程编程的核心在于理解线程的创建方式、生命周期以及线程间的通信。 #### 二、线程...

    java多线程编程总结.pdf

    1. Java多线程基础 Java中的线程可以通过两种方式创建:一种是通过继承Thread类,另一种是实现Runnable接口。Thread类本身就实现了Runnable接口。通过实现Runnable接口的方法通常更为推荐,因为它避免了Java单继承的...

    Java多线程与线程安全实践-基于Http协议的断点续传.zip

    总结起来,Java多线程和线程安全是构建高并发应用的基础,而断点续传技术则是优化大文件传输效率的有效手段。结合两者,可以设计出高效、可靠的文件下载服务。在实践中,我们需要熟练掌握线程的创建与管理,理解各种...

    Java多线程文章系列

    以下是对Java多线程系列文章的详细知识点总结: 1. **线程基础** - **线程的概念**:线程是操作系统分配CPU时间的基本单位,每个线程拥有独立的程序计数器、寄存器和栈。 - **线程创建**:Java提供了两种创建线程...

Global site tag (gtag.js) - Google Analytics