线程必须经常协调他们的活动。最普通的协调方法是保护块(guarded block)。以循环条件开始的代码块必须在循环条件为真时代码块才能够执行。为了正确操作有许多步骤需要遵守。
假设,比如 guardedJoy方法直到共享变量joy被另一个线程修改才会执行。理论上,这个方法一直在循环直到满足条件,但是这个循环很浪费,因为它在等待的时候不停的在运行。
public void guardedJoy() {
// Simple loop guard. Wastes
// processor time. Don't do this!
while(!joy) {}
System.out.println("Joy has been achieved!");
}
一个更有效的保护(guard)是调用Object.wait去暂停当前线程。wait的调用不会返回直到其他的线程发出某个特别的事件已经发生的通知-虽然不见得是这个线程正在等待的事件。(有可能是发生了异常)
public synchronized 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方法。不要假设中断(interrupt)是你要等待的特定条件,因为条件仍然还是true
像许多暂停执行的方法,wait能够抛出InterruptedException。在这个例子中,我们能够忽略这个异常-我们仅仅只关心joy的值
为什么这个版本的guardJoy要同步?假设d是我们用来调用wait的对象。当一个线程调用d.wait,它肯定拥有d的内置锁-否则会抛出一个错误。在同步方法里调用wait方法是一个获得内置锁的简单方法
当wait被调用,线程会释放掉锁并且暂停执行。在未来的某个时间,另外一个线程将会获得同样的锁并且调用Object.notifyAll,这会通知所有在等待lock的线程有一些重要的事情发生了
public synchronized notifyJoy() {
joy = true;
notifyAll();
}
有时在第二个线程释放掉锁后,第一个线程重新获得这个锁并且从调用的wait中返回恢复运行。
注意:有第二个通知方法,notify,只能唤醒一个线程。因为notify不允许你指定要唤醒的线程,这个在大规模并行程序中有用处-即,程序中有大量的线程,并且都在做类似的事情,在这样的程序中,你不必在意哪个线程被唤醒了。
让我们使用保护块(guarded blocks)来创建一个生产者-消费者应用程序。这类程序在线程间共享数据:生产者,负责生产数据,消费者,负责使用数据。这2个线程使用共享对象通信。协调是必不可少的:消费者不能够在生产者递送数据前检索它,并且如果消费者还没有取得老数据生产者不能够递送新数据
在这个例子中,数据是一系列的文本消息,通过一个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) {}
}
}
}
最后,这是main线程,定义在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集合框架中已有的数据结构。更多信息,看问题和练习一章。
分享到:
相关推荐
#### 一、Java并发概述 自Java诞生之初,其设计者就赋予了该语言强大的并发处理能力。Java语言内置了对线程和锁的支持,这使得开发者能够轻松地编写多线程应用程序。本文旨在帮助Java开发者深入理解并发的核心概念...
Java并发编程实践是Java开发中不可或缺的一个领域,它涉及到如何高效、正确地处理多线程环境中的任务。这本书的读书笔记涵盖了多个关键知识点,旨在帮助读者深入理解Java并发编程的核心概念。 1. **线程和进程的...
5. **jcip-annotations-src.jar** 可能包含了Java并发编程实践书籍中使用的自定义注解,例如`@ThreadSafe`表示类是线程安全的,`@NotThreadSafe`表示类不是线程安全的,`@GuardedBy`指定一个字段由哪个锁保护等。...
Java并发编程是Java开发中的重要领域,涉及到多线程、线程安全以及系统性能优化等多个方面。本学习总结将深入探讨并发容器、同步容器、同步工具、死锁、异常处理、线程中断、线程池、返回结果以及同步方法等核心概念...
Java并发编程是软件开发中的重要领域,特别是在多核处理器和分布式系统中,高效地利用并发可以极大地提升程序的性能和响应速度。本资源"Java并发编程_设计原则和模式(CHM)"聚焦于Java语言在并发环境下的编程技巧、...
Java并发编程是Java开发语言中的一个关键领域,特别是在构建高性能、高并发的后端系统时。Apache作为流行的开源软件框架,提供了许多与并发处理相关的工具和库,使得开发者能够更有效地管理和优化多线程环境。本教程...
Java 5以及6在开发并发程序取得了显著的进步,提高了Java虚拟机的性能,提高了并发类的可伸缩性,并加入了丰富的新并发构建块。在本书中,这些便利工具的创造者不仅解释了它们究竟如何工作、如何使用,同时,还阐释...
### Java并发编程知识点详解 #### 一、线程状态与管理 在Java中,线程具有多种状态,这些状态的变化反映了线程在其生命周期中的不同阶段。理解这些状态及其转换对于编写高效、健壮的并发程序至关重要。 - **NEW**...
并发编程是Java语言的一个重要特性,对于面试中涉及Java并发编程的问题,需要有深入的理解和掌握。本文档详细列举了Java并发领域面试中常见的问题,以下是对这些问题及答案的解析。 1.1 多线程和并发问题。并发是指...
Java提供了丰富的并发工具和API,如线程、锁、同步块、原子变量、并发容器等,而《Java并发编程实践》这本书正是对这些工具和最佳实践的详细解读。 1. **线程与进程**:在计算机系统中,线程是程序执行的基本单位,...
### Java并发编程实践笔记知识点详解 #### 一、保证线程安全的方法 1. **不要跨线程访问共享变量:** 当多个线程共享某个变量时,若其中一个线程修改了该变量,其他线程若没有正确同步,则可能读取到错误的数据。...
Java并发编程实战是针对Java并发编程的详细指南,随着多核处理器的普及,多线程并发编程成为了提升应用程序性能的关键技术之一。从Java 5和Java 6版本开始,Java在并发编程方面取得了显著的进步,不仅改进了Java...
根据提供的文件信息,“java并发编程实践 pdf”及“java并发编程pdf”的标题与描述,以及“java 并发编程 多线程”的标签,我们可以推断出这份文档主要讲解了Java语言中的并发编程技术,包括多线程的概念、实现方法...
根据提供的标题、描述和标签,本文将围绕“Java并发编程实践”这一主题展开,深入探讨Java并发编程的基础概念、核心原理以及实际应用技巧。 ### 一、Java并发编程概述 Java并发编程是Java开发中一个非常重要的领域...
Java并发编程实践笔记 Java并发编程实践笔记是一份关于Java并发编程的实践笔记,涵盖了多种关于线程安全、并发编程的实践经验和原则。下面是从笔记中总结的知识点: 1. 保证线程安全的三种方法:不要跨线程访问...
### Java并发核心知识点详解 #### 一、Java并发基础概念 **并发简介**: Java并发是指在一个程序中同时运行多个线程的能力。每个Java程序至少包含一个线程,即启动时运行在`main`方法中的主线程。此外,在Java...