Java并发基础

并发基础


线程

表示一条单独的执行流,有自己自己单独的程序计数器和栈;

1.1 创建方法

  • 继承Thread类
  • 实现Runnable接口
    如果不是调用Thread.start开启线程,而是直接调用其run方法,那就不会有开启一个新线程的作用,这种情况下,run方法只是作为一个普通方法被调用的;

1.2 基本属性

  • id和name
    id是一个递增的整数,每创建一个线程就会加一;
  • 优先级
    Java中1-10,默认为5;
    这里需要注意,设置优先级对于操作 系统而言只是一个建议,编程时不要过分依赖优先级
  • 状态
    可以用Thread的getState()方法得到线程的状态,得到的值是一个枚举类型,如下:

NEW:没有调用start的线程
RUNNABLE:调用start后,正在执行run方法并且没有阻塞的状态;注意:线程在运行或者具备运行条件,只是在等待操作系统调度
BLOCKED:线程在等待锁,视图进入同步块
WAITING: 在等待某个条件
TIMED_WAITING: 在等待超时
TERMINATED: 运行结束后的状态

1.3 基本方法

  • isAlive()
    启动后,run方法运行结束前,返回都是true
  • isDaemon()
    先看下什么是守护线程,对于一般的线程,程序在所有线程都结束后,才会退出,但是对于守护线程,当整个程序剩下的都是daemon线程时,就会退出;
    daemon线程一般是其他线程的辅助线程
  • sleep()
    让线程睡眠指定时间,睡眠期间,该线程会让出CPU;
    注意:这里传入的时间不一定会精准;
  • yield()
    该方法会建议调度器,目前当前线程不着急执行,可以先让其他线程运行
  • join()
    可以让调用join的线程(例如主线程)等待该线程(执行计算的子线程)执行结束

1.4 多线程可能存在的问题

  • 竞态条件:指执行结果不确定,和执行时序有关
    可通过synchroniezd关键字、使用显示锁、使用原子变量解决
  • 内存可见性
    造成这种问题的原因是,数据会被存储在各种高速缓存中,当访问/修改一个变量时,不一定会直接从内存中读取/写入,这就可能导致一个线程对值的修改,另一个线程无法及时更新到;
    可以通过volatile、synchronized关键字或者显示锁方式解决

1.5 优缺点

  • 优点:充分利用CPU和硬件资源,保证GUI及时刷新等
  • 缺点:
    创建线程需要耗费系统资源,为线程创建程序计数器,栈等都是需要开销的;
    线程的切换也是有成本的,主要是上下文切换带来的成本, 当切换时,需要保存当前线程的上下文状态(包括程序计数器的值,CPU寄存器的值等)到内存中

synchronized的理解

2.1 用法

可用于修饰类的:

  • 静态方法
    保护的是当前的类对象
  • 实例方法
    保护的是这个实例,这里需要注意:多个线程是可以同时执行同一个synchronized修饰的实例方法的,只要它们针对的是不同的对象即可。
    因此,需要明确一点:

synchronized修饰实例方法,保护的是当前的实例对象,即this;每一个对象都有一个锁和等待队列,同一时间,锁只能被一个线程所持有;具体执行synchronized实例方法的过程如下:
1) 尝试获得锁,若得到,则执行,否则,加入等待队列,阻塞并等待唤醒
2) 执行方法
3) 释放锁,如果等待队列有线程,则取一个并将其唤醒;注意,如果有多个等待线程,则唤醒哪一个是不一定的,不保证公平性

  • 代码块
    任意对象都有一个锁和等待队列,也就是说任何对象都可以作为锁对象

注意: synchronized关键字保护的是对象而不是具体的代码,理解这一点是很重要的。只要访问的是同一个对象的synchronized方法,即使是不同的代码,也会被保证同步顺序方法。
并且,只能保证加了synchronized修饰的方法同步执行,synchronized方法无法保证非synchronized方法被同时执行;因此,在保护变量时,需要在所有访问该变量的方法上加synchronized修饰。

2.2 特点

  • 可重入性:当其获得了锁后,当进入需要同样锁的代码时,可以直接进入,而无需再等待
  • 提供内存可见性:如果只是为了获得可见性的话,优先考虑更加轻量的volatile关键字
  • 可能产生死锁

当使用synchronized时,需要特别注意修饰的对象是否是同一个,即是否使用了相同的锁;


线程间的协作

3.1 wait/notify

除了用于锁的等待队列, 线程还有另一个等待队列, 表示条件队列,用于线程间的协作。
当调用了wait之后,就会把当前线程加入条件队列并阻塞, 表示当前线程执行不下去了,需要等待一个条件,这个条件自己改变不了,需要其他线程改变,当其他线程改变了条件后,应该调用notify方法。

wait/notify方法只能在synchronized代码块内被调用,否则会抛异常。

wait的具体过程

  1. 把当前的线程加入条件队列,释放对象锁,阻塞等待,线程状态变为WAITING或者TIME_WAITING
  2. 等待时间到或者被其他线程调用notify/notifyAll从条件队列中移除,这时,需要重新竞争对象锁:
    a) 可以获得,线程状态变为RUNNABLE,从wait调用中返回
    b) 无法获得,该线程会加入对象锁等待队列,线程状态变为BLOCKED,获得锁后才会从wait调用中返回;

从wait调用中返回后,不代表其等待条件就一定成立,需要重新检查等待条件:

synchronized (obj) {
    while(条件不成立) {
        obj.wait();
    }
    // do sth
}

调用notify后,并不会释放对象锁,只有在包含notify的synchronized代码块执行结束后,等待的线程才会从wait调用中返回

总结:wait/notify被不同的线程调用,但是二者共享相同的锁和条件等待队列(即相同锁对象的synchronized代码块内),二者围绕一个共享的条件变量进行协作,这个变量是程序自己维护的,当不满足时,wait并进入条件等待队列,另一个线程修改了该条件变量并调用了notify,然后调用wait的线程被唤醒,该线程需要重新检查条件变量。在使用wait/notify时,需要明确协作的共享变量和条件是什么。

3.2 生产者/消费者模式

Java提供的阻塞队列有:

  • BlockingQueue
  • ArrayBlockingQueue
  • LinkedBlockingQueue等

3.3 同时开始

其他线程都先wait,条件满足后notifyAll即可

3.4 等待结束

以未就绪线程数量为条件,一个线程就绪后,将条件-1,当条件为0时,notifyAll即可
Java提供了CountDownLatch用于这种情况

3.5 异步结果

Java提供的主要涉及到的是:

  • 表示异步结果的接口Future和其实现FutureTask
  • 用于执行异步任务的接口Executor,和具有更多功能的子接口ExecutorService
  • 创建上面两种Executor的工厂类Executors

3.5 集合点

当所有线程都执行结束后,到达集合点,交换数据并进行下一步动作;这种和等待结束是类似的;
Java提供了CyclicBarrier


线程的中断

4.1 中断

主要用到的机制是中断,下面来看下中断。
中断并不是强迫终止一个线程,它是一种协作机制,是传递给线程一个取消信号,但何时退出是由线程来决定的。
Java主要提供了下面几个方法:

  • isInterrupted():返回当前线程的中断标志位是否为true
  • interrupt():中断对应的线程
  • static interrupted():返回当前线程的中断标志位是否为true,并清空中断标志位为false

4.2 线程对中断的反应

根据线程当前的状态:调用interrupt()后的变化如下:

  • RUNNABLE:只是设置中断标志位,线程应该自己检查该标志位的状态,例如,如果它是true那就应该退出循环
  • WAITING/TIMED_WAITING:会清空中断标志位,并抛出InterruptedException,该异常是受检查异常,必须处理:
    1) 向上传递
    2) 无法传递时(例如在run方法中),则需要进行合适的清理工作,并调用interrupt方法设置中断标志位,让其他代码知道其发生了中断
  • BLOCKED:只是设置标志位,线程状态不会变化。
    这里需要注意,使用synchronized关键字获取锁的过程中,不会相应中断请求,这时synchronized的局限性,如果这对程序是个问题,那就应该使用显示锁
  • NEW/TERMINATE:无效,标志位也不会变化

4.3 如何取消/关闭线程

如果不清楚线程在做什么,不要贸然使用interrupt方法;
具体的取消方法可以参考原生实现:Future接口的cancle()、ExecutorService的shutdown(),shutdownNow()等

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

推荐阅读更多精彩内容