美文网首页
Java多线程之原子性

Java多线程之原子性

作者: NEU_PROYZ | 来源:发表于2018-05-05 21:08 被阅读98次

    友情提示:作为一个java小白最近在看java多线程知识,东西还是比较多,推荐大家去看《Java多线程编程指南》,怕自己忘了,所以决定码些字。

    开始之前,建议大家一定要系统地学习一下操作系统,并且不能光看网上碎片化的知识点,所以一点一点来吧。

    我准备先回顾一下非常基础而且重要的知识点,先从三个特性下手,原子性,可见性,有序性。每一点的内容都很多,一点一点来,先介绍原子性。

    原子性:

    书本上的定义:对于涉及共享变量访问的操作,若该操作从其执行线程以外的任意线程来看是不可分割的,那么该操作就是原子操作,相应的我们就称该操作是具有原子性的。

    这句话的丰富含义有:

    1.原子操作是对于多线程而言的,对于单一线程,无所谓原子性。有点多线程常识的朋友这个都应该知道,但也要时刻牢记。

    2.原子操作是针对共享变量的。因此,涉及局部变量(如方法中的变量)我们是没必要要求它具有原子性的,

    3.原子操作是不可分割的。(我们要站在多线程的角度)指访问某个共享变量的操作从其执行线程之外的线程来看,该操作要么已经执行完毕,要么尚未发生,其他线程不会看到执行操作的中间结果。学过数据库的朋友应该很熟悉这种原子性。那么,站在访问变量的角度,我们可以这样看,如果要改变一个对象,而该对象包含一组需要同时改变的共享变量,那么,在一个线程开始改变一个变量之后,在其它线程看来,这个对象的所有属性要么都被修改,要么都没有被修改,不会看到部分修改的中间结果。(这只是最简单的一种解释,以后我们还会讲到i++以及初始化等操作)

    好了,这是我们从书上定义角度出发得到没有任何问题的定义,下面我想说说我对原子性的理解(可能有误,欢迎指正)。

    首先保持原子性的重要性不言而喻,这可能是我们学多线程最直观的感受。我们需要让一个共享变量串行的被访问修改,不能造成不一致性。

    这里先说一下互斥性,学过操作系统的同学课上可能都接触过生产者消费者模型,这个模型里面的共享变量就是缓存区的大小。我们学过通过一个互斥变量+临界区来控制两个线程对缓存区的访问实现方式大概是:

    ```

    int mutex = 1;

    while(true){

        wait(mutex);

        critical section

        signal(mutex);

    }

    ```

    上面的这个伪代码大概就是实现互斥访问的机理,wait(mutex)操作相当于:

    ```

    while(mutex <= 0);

    mutex--;

    ```

    wait()操作的意思就是当互斥变量mutex<=0时,就一直阻塞在这里(不停循环)。若mutex>0,其实就是等于1,那么将mutex值变为0,接着执行临界区代码。这就保证其他线程此时想进入临界区时由于得到mutex为0,就一直阻塞。那么我们肯定还要想办法把mutex变回1,不然之后线程就进入不了临界区了。所以,大家就很容易想到signal(mutex)做的事情了:mutex++;

    这里需要注意的是,我们要与多线程里面的wait(),signal()/notify()区别一下。上面讲的互斥变量其实就是的原理。同一时间我们只有一个线程能拥有锁,所以锁具有的排他性。那么,实现锁基本有两种方法:

    1.synchronized(内部锁)/Lock(显示锁)。

    2.CAS(Compare And Swap),这其实是锁的底层实现。之后再细讲这两块。

    说的有点多,拉回来。通过互斥变量(锁)的特性,我们可以实现多个线程执行代码到这个区域的时候必须先获得许可,而且同一时间只有一个线程可以做到,所以咱们就实现了串行化。

    OK,到这里我们实现原子性就很简单了,只需要把对一组共享变量的操作(或者对一个共享变量的多个原子操作)放进临界区就可以了。这样,在执行线程以外的其他线程看来,临界区不管有多少操作都是原子操作。

    当然了,在Java里面要实现这种多线程并发访问这远远不够的。

    我还要强调一个很重要的东西,如何判断一个操作是不是原子操作呢?

    记住,在Java语言中,long型和double型以外的任何类型的变量的写操作都是原子操作。(不提读操作的原因是如果所有线程都是读操作的话,那么没必要保持原子性。我们需要考虑的是read-modify-write和check-then-act这两种形式)

    所以对于基本类型和引用类型的写操作(不是指初始化)都是本身具有原子性的。对于long型和double型我们可以简单理解在32位虚拟机上,我们先对变量的低32位赋值,再对高32位赋值,那么这样两个操作就不是原子操作了,我们很可能读到中间状态。处理这种变量我们可以加一个volatile关键字,之后的文章我会详述这个关键字。

    还有一个命题:原子操作 + 原子操作 != 原子操作

    这里给大家一个很简单的判别方式,也就是我们赋值语句=的右边,(前提是=左边是对共享变量的操作)一旦出现共享变量了,那么就不是原子操作了,因为这肯定涉及读操作和写操作。最容易理解的就是i++(i=i+1)。

    最后,要更好得理解原子性,我们最好去理解一下java内存模型,以及读写操作的过程。这一块很重要,大家不要忽略。

    写的第一篇文章,有问题欢迎指正,下一篇给大家介绍 可见性。

    相关文章

      网友评论

          本文标题:Java多线程之原子性

          本文链接:https://www.haomeiwen.com/subject/layjrftx.html