zoukankan      html  css  js  c++  java
  • jsr133-第一二章

    1:介绍

      java虚拟机支持多线程运行。线程代表的就是Thread class。对用户来说创建线程的唯一办法就是创建一个Thread对象;每一个线程都和一个Thread对象关联。Thread对象调用start()方法就启动了相应的线程。

      线程的表现,尤其是当不能正常同步的时候,会变得混乱和违反直觉。这个规范说明(指:jsr133)描述了java语言里多线程编写的语义定义;它包含了这样的规则:即定义共享内存的读线程能够读到的被多个写线程写入的哪些数据。尽管这个规范类似于不同硬件架构的内存模型,但这是一个java内存模型的语义定义(semantics )。

      这些语义并不是描述一个多线程程序是如何执行的。而是描述了多线程程序允许展现的行为。(the behaviors that multithreaded programs are allowed to exhibit.)

    (Any execution strategy that generates only allowed behaviors is an acceptable execution strategy. )任何执行策略只要产生的是这个规范允许的行为那么它就是可以接受的执行策略。

    1.1 Locks

      多线程之间的沟通有很多种机制。最基础的机制就是同步(synchronization),使用monitors来实现的机制。每个对象关联一个监听器(monitor),一个线程可以锁定或者解锁它。同一时间只有一个线程可以持有一个监视器的锁。任何其他试图锁定已经被锁定的监视器的线程都会被阻塞直到他们持有这个监视器的锁。

      一个线程t可以锁定一个特定的监听器很多次;每一个解锁操作对应一次锁定操作,还原对象状态。

      同步声明(同步块)计算出一个对象的引用;然后尝试执行一个对该对象监视器的锁定操作并且直到锁定完成之前不会继续执行。锁定操作执行后,同步声明中的操作才会被执行。如果同步块的执行完成,不管是正常结束还是异常终端,同一个监视器锁的解锁操作都会自动执行。

      一个同步方法在调用时会自动执行一个锁定操作;在锁定操作完成之前同步方法的内容不会被执行。如果这个方法是一个实例方法,它锁定的是它被调用实例关联的监视器(monitor)(也就是:方法体执行过程中被称为this的这个对象)。如果是静态方法,它锁定的就是方法声明所在的类的类对象对应的监视器。同样的,一旦方法体执行结束,不管是正常的还是异常终止,解锁操作都会在同一个监听器上自动执行。

      这份声明文档(指本文)既不提供也不需要检测死锁的条件。那些多线程持有(直接或间接)多个对象的锁的程序应该使用常用的避免死锁的方法,如果有必要的话,就创建更级别的锁原语。

      其他机制,例如读取和写入Volatile以及在java.util.concurrent 包中的类,提供了正确同步的可选方法。

    1.2 Notation in Examples

      Java内存模型并不是根本上基于java语言的面相对象特性的。为了例子的简洁、简易,我们只展示代码片段而忽略类和方法的定义,或者明确的非关联性。大多数例子是由两个或多个包括访问本地变量,共享全局变量或者一个对象的字段实例的状态的线程组成。我们一般使用类似r1或者r2这样的变量名来表明一个方法或者一个线程的本地变量。这些变量是不能够被其他线程访问的。

     2:Incorrectly Synchronized Programs Exhibit Surprising Behav-iors 错误的同步程序表现出的违反直觉的行为

      Java语法允许编译器和cpu以最优的性能执行,这样与错误的同步代码相互影响,就会产生一些看上去匪夷所思的行为。

      想一下,例如图1所示。这个程序使用本地变量r1和r2 以及 共享变量A和B。 看上去结果应该是:r2 ==2, r1==1 。直觉上,第1步和第3步应该最先执行。如果第1步先执行,那么它就看不到第四部的写操作。如果第3步先执行了,它就看不到第2步执行的写操作。

      如果一些执行表现出这样的行为,那么我们就知道步骤4先于步骤1发生,步骤1先于步骤2,步骤2先于步骤3,步骤3先于步骤4。事实上,这是很荒谬的。

      但是,编译器允许不影响线程独立运行的情况下,对任何一个线程进行重排序。如果步骤1和步骤2进行了重排序,那么r2 == 2和r1 == 1 这样的结果,是不是就有可能发生了。

      对于一些程序来说,这样的行为也许看上去是“broken”。但是,需要被标注的是这个代码是被恰当的同步:

      *  一个线程里对一个变量有一个写操作

      *  另一个线程对同一个变量的读操作

      *  并且这个读操作和写操作并没有按顺序同步

      当这样的情况发生时,就被叫做 a data race。(译:一个数据竞争)。当代码包含一个数据竞争,就会经常发生一些违反直觉的情况。

      一些机器可能会产生如图1那样的执行顺序。just-in-time compiler(即时编译器)和处理器可能会重排序代码。另外,运行的虚拟机的内存层级架构也可能使程序出现看上去好像重排序一样的情况。为了简单起见,我们会简单的像编译器那样涉及到任何能够使代码重排序的东西。源代码到字节码的转换可能重排序和转变程序,但是必须按照这个指定的规范执行。

      另一个结果异常的例子可以看下图2。这个程序也是同步错误的;在访问变量过程中,它没有任何约束的任何顺序访问共享内存。

      

      一个常见编译器优化设计到读取r2的值给r5:因为他们都读取了r1.x的值,并且之间没有任何其他相关的写操作。

      现在考虑这样一种情况,分配给线程2的r6.x的赋值操作介于线程1的第一次r1.x和r3.x的读取。如果编译器决定复用r2给r5,那么r2和r5的将会是0,同时r4将会是3。从这个程序的远景来看,p.x的值已经从0修改成了3,然后又改回来了。

      尽管这样的结果看上去很惊讶,但事实上这是被大多JVM实现锁允许的行为。但同时,被JLS和JVMS这样的传统的虚拟机内存模型所禁止:这是第一个旧的JMM(Java内存模型: Java Memory Model)需要被替换的标志。

     3:Informal Semantics

      当代码被重排序的时候,一个程序必须被正确的同步来避免多种类型的违反直觉的行为发生。使用正确的同步不能保证程序里上述的行为是正确的。但是,使用它允许一个程序员以一种简单的途径来推理出一个程序的可能行为;一个正确同步的程序的行为是极少依赖可能的重排序的。没有正确的同步,非常奇怪的、令人迷惑的和匪夷所思的行为就可能会出现。

      有两个关键的办法来理解一个程序是否正确同步了:

      1:Conflicting Accesses (访问冲突)

        两个访问(读取或者写入)同一个共享字段或者数组元素,如果其中至少有一个访问时写入那么就被称为冲突Conficting。

      2:Happens-Before Relationship 

        两个行为如果是happens-before关系,可以排序。如果一个行为happens-before另一个行为

    期待成为寂寞高手的武林老白
  • 相关阅读:
    分布式系统(五)——容错问题
    分布式系统(四)—— 一致性问题
    分布式系统(三)——选举问题
    分布式系统(二)——互斥问题
    结合中断上下文切换和进程上下文切换分析Linux内核的一般执行过程
    深入理解系统调用
    基于mykernel 2.0编写一个操作系统内核
    微信小程序npm安装pinyin库
    SpringBoot实现下载文件以及前台应当如何对接
    OSX 安装mujoco
  • 原文地址:https://www.cnblogs.com/aquariusm/p/6072531.html
Copyright © 2011-2022 走看看