java并发编程入门引导

博主刚学并发时看了大量的概念,什么各种关键字的内存语义,happens-before 原则,JMM,看完之后依然云里雾里,无法分清主次和联系,希望这篇文章能给初学者启蒙。

为什么要使用并发编程?

并发编程可以提高资源的利用率,发挥多核 CPU 的优势,可以在监听事件的同时进行后台数据处理等。

并发编程需要处理的问题---互斥与同步

假设有两个同时运行的程序,他们都用着各自的资源,且互相不需要通信,那这两个线程就像运行在两台电脑上一样,永远无关。但是我们所说的并发编程通常不是这样的,它们在同一台电脑上运行,且不可避免的需要共享一些资源(如内存区,系统或文件等),其中有一些资源同时只能供一个线程使用 (比如打印机),那么当我们编写程序时,就需要注意这个问题,不能让两个线程同时使用打印机,要不打印出来的就是一串由两种内容穿插而成的乱码了,这就引出了一个需要解决的问题——互斥。
比如这是一段要调用打印机的代码

void Printer(String content) {
    //调用打印机 打印content
    ......
}

我们要做到的,就是保证这段代码同时只有一个线程在执行,这种代码块可以称之为临界区。

但是,这还是不够的,线程之间的关系有时并不是简单的互斥,而是需要交换信息,比如线程 A 生产出了一本书的 1,4,5 章节,而线程 B 生产出了一本书的 2,3,6 章节,它们需要互相通信协作完成整本书的有序打印,那就需要解决比互斥更为复杂的情况了——线程的同步。

同步可以说是一种更为复杂的互斥,而互斥是一种特殊的同步。

保证程序同步面临的难题---缓存与重排序

首先,假设计算机没有缓存,也没有对指令进行重排序(你现在不需要知道啥是缓存和重排序,反正现在假设它没有),我们先在这种理想状态下来解决一个同步问题:

  • 先让一个线程打印第一页,再让另一个线程打印第二页
/*
 * 这段代码表示先打印完第一页,再打印第二页
 * 打印完第一页,ThreadA 将 page 置为 1,
 * ThreadB 检测到如果 page 为 1,打印第二页
 */
class alternatePrinter {
    int page = 0;

    class ThreadA implements Runnable {
        void run() {
            Printer("打印第一页"); //1
            page = 1; //2
        }
    }

    class ThreadB implements Runnable {
        void run() {
            while(true) {
                if (page == 1) {
                    Printer("打印第二页");
                    break;
                }
            }
        }
    }
}

如果没有缓存和重排序,这样写完全可以保证只有先打印出第一页才会打印第二页。但是,计算机和编译器在编译时和运行时会打乱程序的指令,以配合计算机底层的一些结构使程序运行的更快。那你可能惊呆了,我的程序要不是按我写的顺序执行,那岂不是全乱套了吗。但其实它是在保证了单线程下不改变任何运行结果的情况下进行的重排序,对有数据依赖的指令是不会乱动的(参考 as-if-serial 语义),但是对于单线程的执行不改变运行结果,并不意味着对多线程的执行不改变运行结果,想一下,上图程序中的 1 和 2(看注释),如果改变了执行顺序(2 在 1 前执行,这样不会改变单线程中的运行结果),就会出现先打印出第二页,再打印第一页的错误情况。

不仅是重排序,缓存的存在也会让我们的多线程程序执行出让人意外的结果,比如下面这段代码:

public class TestMain {
    int a = 0;

    class ThreadA implements Runnable {
        public void run() {
            for (int i = 0; i < 3; i++)
                a = a + 1;
        }
    }

    class ThreadB implements Runnable {
        public void run() {
            for (int i = 0; i < 3; i++)
                a = a + 1;
        }
    }
}

这段代码当线程 A 和 B 都执行完后,a 的值可能是 3 到 6 中的任意值,因为在 java 内存模型中,每个线程都有它自己的本地内存(缓存),和一个主内存,就像计组原理中的主内存和每个 cpu 各自的一级缓存,二级缓存关系是一样的。



这样,在多线程的情况下,读入和写入的可能都是自己的本地内存中变量的副本,线程A 对 a 值的操作可能对线程B 是不可见的,如果在某一时刻,对于变量 a,本地内存A 的副本为 0,本地内存B 的副本也为 0,这两个线程分别对 a 进行加 1 操作,然后又把它们同步到主内存中,那么主内存中的 a 的值就变成了 1,而我们想要的是 2。

解决缓存与重排序问题---happens-before原则及它的底层实现

这篇文章对 JMM happens-before原则 内存屏障的解释都不够完整,文章旨在捋清它们之间的关系,如果想进一步学习,博主推荐《java并发编程的艺术》
计算机对程序的优化(重排序与缓存)使我们对多线程同步的控制变的无法进行,但是如果我们完全摒弃它们来实现对多线程程序的控制,就有点 得之桑榆,失之东隅 的感觉了,好在编程语言的设计者想到了一个折衷的办法,发明了 JMM(java内存模型),这套规范规定了 java 中一系列与同步相关的关键字(synchronize,volatile,lock相关)与它们所遵循的原则(happens-before原则)和这些原则的具体实现方法(插入内存屏障),下面就展开说一下这些内容,先来一张图:


然后解释一下 happens-before 原则的定义:

  • 如果一个操作 happens-before 另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。(JMM 对程序员的的承诺)
  • 两个操作之间存在 happens-before 关系,并不意味着 Java 平台的具体实现必须要按照 happens-before 关系指定的顺序来执行。如果重排序之后的执行结果,与 happens-before 关系来执行的结果一致,那么这种重排序并不非法(JMM 对编译器和处理器重排序的约束原则)

再举一个例子:

  • 关键字:volatile
  • 它所遵循的 happens-before 原则:对一个 voaltile 域的写,happens-before 于任意后续对这个 volatile 域的读。
  • 底层如何实现这个原则:
    在每个 volatile 写操作的前面插入一个 StoreStore 屏障
    在每个 volatile 写操作的后面插入一个 StoreLoad 屏障
    在每个 volatile 读操作的后面插入一个 LoadLoad 屏障
    在每个 volatile 读操作的后面插入一个 LoadLoad 屏障
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,123评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,031评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,723评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,357评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,412评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,760评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,904评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,672评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,118评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,456评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,599评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,264评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,857评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,731评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,956评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,286评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,465评论 2 348