java多线程编程基础三-线程协作

wait/notify(通知与唤醒)

Object.wait()/Object.wait(long):是执行线程暂停(生命周期状态变为WAITING)
Object.notify()/Object.notifyAll():唤醒被暂停的线程

等待线程和通知线程必须调用同一个对象的wait方法、notfiy方法来实现等待和通知。
调用一个对象的notify方法所唤醒的线程仅是该对象上的一个任意等待线程。
notify方法调用应该尽可能地放在靠近临界区结束的地方。

wait/notify面临的问题:

  1. 过早唤醒:在线程还不满足保护条件的时候唤醒线程
  2. 信号丢失:在线程调用wait方法之前没有判断保护条件,造成通知线程在进入临界区之前就更新了相关共享变量,满足条件直接进行通知。等待线程失去接收通知,一直处于等待状态。
  3. 欺骗性唤醒:在其他线程未调用notify或notifyAll方法时被唤醒。
  4. 上下文切换

Object.notify()

  1. 唤醒的是其所属对象上的任意一个等待线程,并且Object.notify()本身在唤醒线程时是不考虑保护条件的。
  2. 可能导致信号丢失

Object.notifyAll()

  1. 方法唤醒的是其所属对象上的所有等待线程。
  2. 效率不高(1照成)

使用Object.notify()替换Object.notifyAll()时需要满足的条件:

  1. 一次通知仅需唤醒至多一个线程
  2. 相应对象的等待集中仅包含同质等待线程。
    同质等待线程:指这些线程使用同样的保护条件,并且这些线程在调用wait方法后的执行逻辑一致。
    典型的同质线程:使用一个Runnable接口创建的不同线程,或者从同一个Thread子类中new出来的多个实例

Thread.join():使当前线程等待目标线程完成后继续进行
Thread.join(long):当目标线程在指定的时间内没有完成时,当前线程依然继续执行。

java条件变量

java.util.concurrent.locks.Condition接口,可以代替wait/notify实现等待/通知,为解决过早唤醒问题提供了支持;并解决了Object.wait(long)不能区分返回是否由等待超时而导致的问题(使用Condition.awaitUntil(date),返回ture即为等待未超时)

Condition接口本身只是对解决过早唤醒问题提供了支持。要真正解决过早唤醒问题,我们需要通过应用代码维护保护条件与条件变量之间的对应关系,即使用不同的保护条件的等待线程需要调用不同的条件变量的await 方法来实现其等待,并使通知线程在更新了相关共享变量之后,仅调用与这些共享变量有关的保护条件所对应的条件变量的signal/signalAll 方法来实现通知。

倒计时协调器:CountDownLatch
CountDownLatch可以实现一个(或者多个)线程,等待其他线程完成一组特定的操作之后才继续执行。这组操作被称为“先决操作”

CountDownLatch 内部会维护一个用于表示未完成的先决操作数量的计数器。CountDownLatch.countDown()每被执行一次就会使相应实例的计数器值减少l。CountDownLatch. await()相当于一个受保护方法,其保护条件为“计数器值为0”(代表所有先决操作巳执行完毕),目标操作是一个空操作。因此,当计数器值不为0时CountDownLatch.await()的执行线程会被暂停,这些线程就被称为相应CountDownLatch上的等待线程。CountDownLatch.countDown()相当于一个通知方法,它会在计数器值达到0的时候唤醒相应实例上的所有等待线程。

栅栏(CyclicBarrier):
有时候多个线程可能需要相互等待对方执行到代码中的某个地方(集合点),这时这些钱程才能够继续执行。CyclicBarrier就实现了这种等待。

使用CyclicBarrier实现等待的线程被称为参与方(Party)。参与方只需要执行CyclicBarrier.await()就可以实现等待。尽管从应用代码的角度来看,参与方是并发执行CyclicBarrier.await()的,但是,CyclicBarrier内部维护了一个显式锁,这使得其总是可以在所有参与方中区分出一个最后执行CyclicBarrier.await()的线程,该线程被称为最后一个线程。除最后一个钱程外的任何参与方执行CyclicBarrier.await()都会导致该线程被暂停(线程生命周期状态变为WAITING)。最后一个线程执行CyclicBarrier.await()会使得使用相应CyclicBarrier实例的其他所有参与方被唤醒,而最后一个线程自身并不会被暂停。

CyclicBarrier内部使用了一个条件变量trip来实现等待/通知。CyclicBarrier内部实现使用了分代(Generation)的概念用于表示CyclicBarrier实例是可以重复使用的。除最后一个线程外的任何一个参与方都相当于一个等待线程,这些线程所使用的保护条件是“当前分代内,尚未执行await方法的参与方个数(parties)为0”。当前分代的初始状态是parties等于参与方总数(通过构造器中的parties参数指定)。CyclicBarrier.await()每被执行一次会使相应实例的parties值减少l。最后一个线程相当于通知线程,它执行CyclicBarrier.await()会使相应实例的parties值变为0,此时该线程会先执行barrierAction.run(),然后再执行trip.signalAll()来唤醒所有等待线程。接着,开始下一个分代,即使得CyclicBarrier的parties值又重新恢复为其初始值。

信号量(Semaphore)
JDK1.5中引人的标准库类java.util.concurrent.Semaphore可以用来实现流量控制。
Semaphore相当于虚拟资源配额管理器,它可以用来控制同一时间内对虚拟资源的访问次数。为了对虚拟资源的访问进行流量控制,我们必须使相应代码只有在获得相应配额的情况下才能够访问这些资源。为此,相应代码
在访问虚拟资源前必须先申请相应的配额,并在资源访问结束后返还相应的配额。Semaphore.acquire()/release()分别用于申请配额和返还配额。Semaphore.acquire()在成功获得一个配额后会立即返回。如果当前的可用配额不足,那么Semaphore.acquire()会使其执行线程暂停。Semaphore内部会维护一个等待队列用于存储这些被暂停的钱程。Semaphore.acquire()在其返回之前总是会将当前的可用配额减少l。Semaphore.release()会使当前可用配额增加l,井唤醒相应Semaphore实例的等待队列中的一个任意等待线程。

管道:线程间的直接输出与输入
Java 标准库类PipedOutputStream 和PipedInputStream是生产者一消费者模式的一个具体例子。PipedOutputStream和PipedlnputStream分别是OutputStream和InputStream的一个子类,它们可用来实现线程间的直接输出和输入。所谓“直接”是指从应用代码的角度来看,一个线程的输出可作为另外一个线程的输入,而不必借用文件、数据库、网络连接等其他数据交换中介。
PipedOutputStream相当于生产者,其生产的产品是字节形式的数据;PipedlnputStream相当于消费者。PipedlnputStream内部使用byte型数组维护了一个循环缓冲区(CircularBuffer),这个缓冲区相当于传输通道。在使用PipedOutputStream 、PipedInputStream进行输出、输入操作前,PipedOutputStream实例和PipedInputStream实例需要建立起关联(Connect)。建立关联的PipedOutputStream 实例和PipedInputStream 实例就像一条输送水流的管道,管道的一端连着注水口(PipedOutputStream),另一端连着出水口(PipedInputStream)。这样,生产者所生产的数据(相当于水流)通过向PipedOutputStream实例输出(相当于向管道注水),就可以被消费者通过关联的PipedInputStream实例所读取(相当于从出水口接水)。PipedOutputStream 实例和PipedInputStream 实例之间的关联可以通过调用各自实例的connect方法实现,也可以通过在创建相应实例的时候将对方的实例指定为自己的构造器参数来实现。

PipedOutputStream 和PipedlnputStream 适合在单生产者一单消费者模式中使用。生产者线程发生异常而导致其无法继续提供新的数据时,生产者线程必须主动提前关闭相应的PipedOutputStream实例(调用PipedOutputStream.close())。

双缓冲和Exchange:
缓冲(Buffering)是一种常用的数据传递技术。缓冲区相当于数据源(Source,即数据的原始提供方)与数据使用方(Sink)之间的数据容器。从这个角度来看,数据源相当于生产者,数据使用方相当于消费者。数据源所提供的数据相当于产品,而缓冲区可被看作产品的容器或者外包装。在多线程环境下,有时候我们会使用两个(或者更多)缓冲区来实现数据从数据源到数据使用方的移动。其中一个缓冲区填充满来自数据源的数据后可以被数据使用方进行“消费”,而另外一个空的(或者已经使用过的)缓冲区则用来填充来自数据源的新的数据。这里,负责填充缓冲区的是一个线程(生产者线程),而使用已填充完毕的另外一个缓冲区的则是另外一个线程(消费者线程)。因此,当消费者线程消费一个已填充的缓冲区时,另外一个缓冲区可以由生产者线程进行填充,从而实现了数据生成与消费的并发。这种缓冲技术就被称为双缓冲( Double Buffering )。

JDKl.5中引人的标准库类java.util.concurrent.Exchanger 可以用来实现双缓冲。
Exchanger相当于一个只有两个参与方的CyclicBarrier。Exchanger.exchange(V)相当于CyclicBarrier.await()。

线程中断机制:Thread.interrupted()
一个线程请求另外一个线程停止其当前正在执行的任务。中断只是一个请求,该请求是否会被满足,取决于目标线程的处理。

线程停止:线程停止的场景:系统或服务关闭、错误处理、用户取消
java并没有提供直接停止线程的接口。(Thread.stop()已经被废弃)

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

推荐阅读更多精彩内容