守护阻塞
线程通常必须协调他们的操作,最常见的协调用法是守护阻塞,这样的阻塞首先轮询一个条件,该条件必须为真,然后阻塞才能继续,要正确执行此操作,需要执行许多步骤。
例如,假设guardedJoy是一个方法,在另一个线程设置了共享变量joy之前,该方法不能继续,理论上,这种方法可以简单地循环直到满足条件,但该循环是浪费的,因为它在等待时持续执行。
public void guardedJoy() { // Simple loop guard. Wastes // processor time. Don"t do this! while(!joy) {} System.out.println("Joy has been achieved!"); }
更有效的守护是调用Object.wait来挂起当前线程,在另一个线程发出可能发生某些特殊事件的通知之前,wait的调用不会返回 — 尽管不一定是这个线程正在等待的事件:
public synchronized void guardedJoy() { // This guard only loops once for each special event, which may not // be the event we"re waiting for. while(!joy) { try { wait(); } catch (InterruptedException e) {} } System.out.println("Joy and efficiency have been achieved!"); }
始终在测试等待条件的循环内调用wait,不要假设中断是针对你正在等待的特定条件,或者条件仍然是true。
像许多暂停执行的方法一样,wait会抛出InterruptedException,在这个例子中,我们可以忽略该异常 — 我们只关心joy的值。
为什么这个版本的guardedJoy是同步的?假设d是我们用来调用wait的对象,当一个线程调用d.wait时,它必须拥有d的固有锁 — 否则抛出一个错误,在同步方法中调用wait是获取固有锁的一种简单方法。
当调用wait时,线程释放锁并暂停执行,在将来的某个时间,另一个线程将获取相同的锁并调用Object.notifyAll,通知等待该锁的所有线程发生了重要的事情:
public synchronized notifyJoy() { joy = true; notifyAll(); }
在第二个线程释放锁之后的一段时间,第一个线程重新获取锁并通过从调用wait的返回来恢复。
还有第二种通知方法notify,它唤醒单个线程,因为notify不允许你指定被唤醒的线程,所以它仅在大规模并行应用程序中有用 — 也就是说,具有大量线程的程序,都做类似的事,在这样的应用程序中,你不关心哪个线程被唤醒。
让我们使用守护阻塞来创建生产者—消费者应用程序,这种应用程序在两个线程之间共享数据:创建数据的生产者和使用数据的消费者。两个线程使用共享对象进行通信,协调至关重要:消费者线程在生产者线程交付之前不得尝试检索数据,如果消费者未检索到旧数据,则生产者线程不得尝试传递新数据。
在此示例中,数据是一系列文本消息,通过Drop类型的对象共享:
public class Drop { // Message sent from producer // to consumer. private String message; // True if consumer should wait // for producer to send message, // false if producer should wait for // consumer to retrieve message. private boolean empty = true; public synchronized String take() { // Wait until message is // available. while (empty) { try { wait(); } catch (InterruptedException e) {} } // Toggle status. empty = true; // Notify producer that // status has changed. notifyAll(); return message; } public synchronized void put(String message) { // Wait until message has // been retrieved. while (!empty) { try { wait(); } catch (InterruptedException e) {} } // Toggle status. empty = false; // Store message. this.message = message; // Notify consumer that status // has changed. notifyAll(); } }
Producer中定义的生产者线程发送一系列熟悉的消息,字符串“DONE”表示已发送所有消息,为了模拟真实世界应用程序的不可预测性,生产者线程在消息发送之间暂停随机间隔。
import java.util.Random; public class Producer implements Runnable { private Drop drop; public Producer(Drop drop) { this.drop = drop; } public void run() { String importantInfo[] = { "Mares eat oats", "Does eat oats", "Little lambs eat ivy", "A kid will eat ivy too" }; Random random = new Random(); for (int i = 0; i < importantInfo.length; i++) { drop.put(importantInfo[i]); try { Thread.sleep(random.nextInt(5000)); } catch (InterruptedException e) {} } drop.put("DONE"); } }
在Consumer中定义的消费者线程只是检索消息并将其打印出来,直到它检索到“DONE”字符串,该线程也会暂停随机间隔。
import java.util.Random; public class Consumer implements Runnable { private Drop drop; public Consumer(Drop drop) { this.drop = drop; } public void run() { Random random = new Random(); for (String message = drop.take(); ! message.equals("DONE"); message = drop.take()) { System.out.format("MESSAGE RECEIVED: %s%n", message); try { Thread.sleep(random.nextInt(5000)); } catch (InterruptedException e) {} } } }
最后,这是在ProducerConsumerExample中定义的主线程,它启动生产者和消费者线程。
public class ProducerConsumerExample { public static void main(String[] args) { Drop drop = new Drop(); (new Thread(new Producer(drop))).start(); (new Thread(new Consumer(drop))).start(); } }
Drop类是为了演示守护阻塞而编写的,为了避免重新造轮子,在尝试编写自己的数据共享对象之前,检查Java集合框架中的现有数据结构。上一篇:并发活性 下一篇:不可变对象
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/73055.html
并发活性 并发应用程序及时执行的能力被称为其活性,本节描述了最常见的活性问题,死锁,并继续简要描述其他两个活性问题,饥饿和活锁。 死锁 死锁描述了两个或多个线程永远被阻塞,等待彼此的情况,这是一个例子。 Alphonse和Gaston是朋友,是礼貌的忠实信徒,礼貌的一个严格规则是,当你向朋友鞠躬时,你必须一直鞠躬,直到你的朋友有机会还礼,不幸的是,这条规则没有考虑到两个朋友可能同时互相鞠躬的可能性...
摘要:多线程和并发问题是技术面试中面试官比较喜欢问的问题之一。线程可以被称为轻量级进程。一个守护线程是在后台执行并且不会阻止终止的线程。其他的线程状态还有,和。上下文切换是多任务操作系统和多线程环境的基本特征。 多线程和并发问题是 Java 技术面试中面试官比较喜欢问的问题之一。在这里,从面试的角度列出了大部分重要的问题,但是你仍然应该牢固的掌握Java多线程基础知识来对应日后碰到的问题。(...
摘要:线程可以被称为轻量级进程。一个守护线程是在后台执行并且不会阻止终止的线程。其他的线程状态还有,和。上下文切换是多任务操作系统和多线程环境的基本特征。在的线程中并没有可供任何对象使用的锁和同步器。 原文:Java Multi-Threading and Concurrency Interview Questions with Answers 翻译:并发编程网 - 郑旭东 校对:方腾飞 多...
摘要:时间年月日星期六说明本文部分内容均来自慕课网。慕课网教学源码无学习源码第一章课前准备前言课程说明比较和这两种线程创建的方式,需要知道和的基本创建方式。一旦主线程获取到了用户的输入,这时候,阻塞就会解除掉,主线程继续运行,直到结束。 时间:2017年07月08日星期六说明:本文部分内容均来自慕课网。@慕课网:http://www.imooc.com教学源码:无学习源码:https://g...
摘要:死亡线程方法执行结束,或者因异常退出了方法,则该线程结束生命周期。死亡的线程不可再次复生。直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。枚举程序中的线程。强迫一个线程等待。通知一个线程继续运行。 一. 线程状态转换图 showImg(https://segmentfault.com/img/bV38ef?w=968&h=680); 线程间的状态转换说明: 新建(new)...
阅读 3366·2021-11-24 09:39
阅读 1778·2021-11-17 09:33
阅读 3434·2021-10-12 10:12
阅读 4953·2021-09-22 15:51
阅读 1097·2019-08-30 13:11
阅读 3552·2019-08-30 10:59
阅读 546·2019-08-30 10:48
阅读 1296·2019-08-26 13:48