JAVA线程基础回顾及内存模型(二)

原子性 可见性和有序性

  • 原子性(Atomicity):由JMM直接保证原子性变量操作在上节的read\loadstore\writeuse\assign操作中介绍过了,对于long和double两个64位的非原子性协定知道即可。我们可以认为对基本数据类型的访问读写都是具有原子性的。而要保证一个更大的范围的原子性,JMM提供了lock\unlock来满足需求,语言层面就是使用synchronized关键字。
  • 可见性(Visibility):可见性是指一个线程修改了共享变量的值,其他线程能够立刻得知这个修改。volatile关键字中我们已经讲了这一点。volatilesynchronized都保证了可见性,另一种保证可见性的关键字是final,这是显然的,一个被final修饰的变量一旦被赋值就不能再更改了,这与线程也没有什么关系。
  • 有序性(Ordering):程序代码按照指令顺序执行。
    • 如果在本线程内观察,所有的操作都是有序的,指“线程内表现为串行的语义”(Within-Thread As-If-Serial Semantics);如果在一个线程中观察另一个线程,所有的操作都是无序的,指“指令重排序”现象和“工作内存与主内存同步延迟”现象。
    • 提供两个关键字保证有序性:volatile 本身就包含了禁止指令重排序的语义;synchronized保证一个变量在同一个时刻只允许一条线程对其进行lock操作,使得持有同一个锁的两个同步块只能串行地进入。

关于volatile禁止指令重排序保证有序性的介绍,可以参考这篇文章——volatile关键字,举了个单例模式的例子,解释了volatile禁止指令重排序的效果。

public class Singleton {
    private static Singleton uniqueInstance;
    private Singleton(){}
    public static Singleton getInstance(){
        if(uniqueInstance == null){
        // B线程检测到uniqueInstance不为空
            synchronized(Singleton.class){
                if(uniqueInstance == null){
                    uniqueInstance = new Singleton();
                    // A线程被指令重排了,刚好先赋值了;但还没执行完构造函数。
                }
            }
        }
        return uniqueInstance;// 后面B线程执行时将引发:对象尚未初始化错误。
    }
}

如果加上volatile之后,就没有这样的问题了。volatile保证被其修饰的变量不会被编译器重排序,但是其他代码还是可能会被重排序的。


线程控制的几个方法

我们之前只是用start来启动线程。下面再介绍几个API,用来控制线程达到其他的状态

暂停线程

首先是静态sleep()方法:

Thread.sleep(long millis) // 静态方法,让调用这个方法的线程让出 CPU,休眠参数指定的毫秒数

调用这个方法的线程会进入阻塞状态。

其次是实例join()方法:

Thread.join() // 实例方法,分为有参数版本和无参数版本,
              // 调用这个方法的线程会让出 CPU 资源进行等待参数指定的时间(毫秒),如果没有指定参数,
              // 那么会直到这个方法所属的线程对象执行完成后,陷入等待的线程会恢复就绪态,等待 CPU 资源

调用这个方法的线程会进入阻塞状态,CPU会让给这个方法所属的线程对象。

终止线程

run()方法执行完毕是正常的终止线程,但也可以人为调用方法来终止线程的执行。
Thread类有一个stop()方法,这个方法已经被废弃了,是不安全的,具体废弃原因去查文档。可以利用一个boolean变量,这样安全的终止一个线程:

public void run() {
    boolean isFinish = false; // 记录线程任务是否完成
    while (!isFinish) {
        if(/*任务完成*/) {
            break; // 或者 isFinish = true;
        } else {
            // do something ...
        }
    }
}

Thread类中还提供了个interrup()以及相关的一些方法,这是实例方法,可以这样做:

public void run() {
    while (Thread.currentThread().isInterrupted() == false) {
        if (/*任务完成*/) {
            Thread.currentThread().interrupt();
        } else {
            // do something ...
        }
    }
}

详细了解上面的几个方法之前,先知道一个中断标志的概念,中断标志可以理解成线程内部的一个 boolean 类型的字段,其本身不会影响线程的执行,但是和其他方法(下面介绍的会有sleep方法以及wait方法)混用时,就有可能影响线程的执行。

Thread.currentThread() // 静态方法,返回执行当前代码的线程对象引用

Thread.isInterrupted() // 实例方法,返回调用这个方法的线程对象的中断标志(true / false)

Thread.interrupt() // 实例方法,将调用这个方法的线程对象的中断标志设置为 true,
                   // 请注意:线程的中断标志本身不会影响线程的执行

直接用interrup自然比自己去定义boolean更方便,但是我们介绍这个中断标志就是有伏笔的,interrup会把中断标志设置为true,而这和sleep方法一起使用时候,会有异常抛出,我们打开sleep的源码:



从注释中看到,sleep方法调用时候,如果当前线程被中断(它的中断标志是true),那么在抛出异常时候这个中断标志会被清除(将中断标志设置为 false),由此就导致了isInterrupted方法的返回值,可能并不是我们想要的结果。

其他方法

静态yield()方法,提示scheduler,让出调用线程的资源,一定概率。

Thread.yield() // 静态方法,提示线程调度器当前调用这个方法的线程(当前持有 CPU 资源的线程)已经完成任务,
               // 可以让出 CPU 资源了,当然,这只是一种提示,线程调度器可以忽略这种提示,
               // 所以 CPU 资源是否让出并不是一定的,是有一定概率的。

上面介绍的所有方法都是Thread类中的方法,有的是静态的,有的是实例方法,静态的这些方法一般都直接对调用它的线程起到作用,而实例方法,则还对这个方法所属的对象线程有影响。下面我们再来看Object类中一些与线程控制有关的方法。

  • Object.wait()
  • Object.notify()
  • Object.notifyAll()

这些方法全部是实例方法。都是必须要在sychronized方法或者sychronized代码块中才能使用,而且还必须是某个线程已经获取到了这个Object的锁时候,才能调用它的wait,notify,notifyAll。

Object.wait() // 使得调用这个方法的线程释放这个 Object 对象的锁并且陷入无限等待,
              // 直到某个线程调用了这个 Object 对象的 notify 或者 notifyAll 方法
              // 线程被唤醒之后进入就绪状态,等待 CPU 资源
              // 如果当前线程的中断标志为 true,那么会抛出一个 InterruptedException 异常

Object.wait(long timeout) 
// 使得调用这个方法的线程释放这个 Object 对象的锁并且等待参数指定的时间,单位为毫秒
// 直到这个等待的时间段过去、某个线程调用了这个 Object 对象的 notify 或者 notifyAll 方法
// 线程被唤醒之后进入就绪状态,等待 CPU 资源
// 如果当前线程的中断标志为 true,那么会抛出一个 InterruptedException 异常

Object.wait(long timeout, int nanos) 
// 使得调用这个方法的线程释放这个 Object 对象的锁并且等待参数指定的时间,
// 第二个参数是纳秒,提供更加精确的控制
// 直到这个等待的时间段过去、某个线程调用了这个 Object 对象的 notify 或者 notifyAll 方法
// 线程被唤醒之后进入就绪状态,等待 CPU 资源
// 如果当前线程的中断标志为 true,那么会抛出一个 InterruptedException 异常

Object.notify() // 唤醒一个因调用这个 Object 对象的 wait() 方法而陷入等待状态的线程,具体哪个线程未知。

Object.notifyAll() // 唤醒所有因调用这个 Object 对象的 wait() 方法而陷入等待状态的线程。

使用实例还是可以参考这个老哥的文章——wait使用的一个实例——转账余额不足

注意wait和sleep的区别,他们好像都多少有些等待、休眠的意思。但是前者是Object类的实例方法,调用后会释放当前对象锁,并且需要其他线程调用这个对象的notify()或者notifyAll()来唤醒。而后者是Thread类的静态方法,调用后只是让出CPU,并不会释放锁,监控状态一直保持,过了指定时间后它会自动恢复运行状态。

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

推荐阅读更多精彩内容

  • 本文是我自己在秋招复习时的读书笔记,整理的知识点,也是为了防止忘记,尊重劳动成果,转载注明出处哦!如果你也喜欢,那...
    波波波先森阅读 11,247评论 4 56
  • 本文首发于我的个人博客:尾尾部落 本文是我刷了几十篇一线互联网校招java后端开发岗位的面经后总结的多线程相关题目...
    繁著阅读 2,001评论 0 7
  • 天净沙·军训 碧天绿树草坪,教官老师学生, 军姿跑步队形。烈日当空, 新学子苦练中。
    攻她城做她王_6acc阅读 1,813评论 0 1
  • 当你在山脚时, 不要羡慕山顶的风景。 山顶云雾缭绕, 有会当凌绝顶的壮美。 山脚亦有鸟语花香, 小桥流水的静谧。 ...
    行_者阅读 368评论 0 0
  • 去年1月份,表姐拿了几只螃蟹,会做饭的老爸不在家,我只能看着蟹干瞪眼,无从下手。在家我也只不过是会做家常菜的活,海...
    沐沐不红阅读 278评论 0 0