基本线程同步(四)在同步代码中使用条件

在同步代码中使用条件

在并发编程中的一个经典问题是生产者与消费者问题,我们有一个数据缓冲区,一个或多个数据的生产者在缓冲区存储数据,而一个或多个数据的消费者,把数据从缓冲区取出。

由于缓冲区是一个共享的数据结构,我们必须采用同步机制,比如synchronized关键字来控制对它的访问。但是我们有更多的限制因素,如果缓冲区是满的,生产者不能存储数据,如果缓冲区是空的,消费者不能取出数据。

对于这些类型的情况,Java在Object对象中提供wait(),notify(),和notifyAll() 方法的实现。一个线程可以在synchronized代码块中调用wait()方法。如果在synchronized代码块外部调用wait()方法,JVM会抛出IllegalMonitorStateException异常。当线程调用wait()方法,JVM让这个线程睡眠,并且释放控制 synchronized代码块的对象,这样,虽然它正在执行但允许其他线程执行由该对象保护的其他synchronized代码块。为了唤醒线程,你必 须在由相同对象保护的synchronized代码块中调用notify()或notifyAll()方法。

在这个指南中,你将学习如何通过使用synchronized关键字和wait()和notify(),notifyAll()方法实现生产者消费者问题。

准备工作

这个指南的例子使用Eclipse IDE实现。如果你使用Eclipse或其他IDE,如NetBeans,打开它并创建一个新的Java项目。

如何做…

按以下步骤来实现的这个例子:

1.创建EventStorage类,包括一个名为maxSize,类型为int的属性和一个名为storage,类型为LinkedList<Date>的属性。

1 public class EventStorage {
2 private int maxSize;
3 private List<Date> storage;

2.实现这个类的构造器,初始化所有属性。

1 public EventStorage(){
2 maxSize=10;
3 storage=new LinkedList<>();
4 }

3. 实现synchronized方法set(),用来在storage上存储一个事件。首先,检查storage是否已满。如果满了,调用wait()方 法,直到storage有空的空间。在方法的尾部,我们调用notifyAll()方法来唤醒,所有在wait()方法上睡眠的线程。

01 public synchronized void set(){
02 while (storage.size()==maxSize){
03 try {
04 wait();
05 } catch (InterruptedException e) {
06 e.printStackTrace();
07 }
08 }
09 storage.offer(new Date());
10 System.out.printf("Set: %d",storage.size());
11 notifyAll();
12 }

4. 实现synchronized方法get(),用来在storage上获取一个事件。首先,检查storage是否有事件。如果没有,调用wait()方 法直到,storage有一些事件,在方法的尾部,我们调用notifyAll()方法来唤醒,所有在wait()方法上睡眠的线程。

01 public synchronized void get(){
02 while (storage.size()==0){
03 try {
04 wait();
05 } catch (InterruptedException e) {
06 e.printStackTrace();
07 }
08 }
09 System.out.printf("Get: %d: %s",storage.
10 size(),((LinkedList<?>)storage).poll());
11 notifyAll();
12 }

5.创建Producer类,并指定它实现Runnable接口,它将实现这个示例的生产者。

1 public class Producer implements Runnable {

6.声明一个EventStore对象,并实现(Producer类)构造器,初始化该对象。

1 private EventStorage storage;
2 public Producer(EventStorage storage){
3 this.storage=storage;
4 }

7.实现run()方法,该方法调用EventStorage对象的set()方法100次。

1 @Override
2 public void run() {
3 for (int i=0; i<100; i++){
4 storage.set();
5 }
6 }

8.创建Consumer类,并指定它实现Runnable接口,它将实现这个示例的消费者。

1 public class Consumer implements Runnable {

9.声明一个EventStore对象,并实现(Consumer类)构造器,初始化该对象。

1 private EventStorage storage;
2 public Consumer(EventStorage storage){
3 this.storage=storage;
4 }

10.实现run()方法,该方法调用EventStorage对象的get()方法100次。

1 @Override
2 public void run() {
3 for (int i=0; i<100; i++){
4 storage.get();
5 }
6 }

11.通过创建类名为Main,且包括main()方法来实现这个示例的主类。

1 public class Main {
2 public static void main(String[] args) {

12.创建一个EventStorage对象。

1 EventStorage storage=new EventStorage();

13.创建一个Producer对象,并且用线程运行它。

1 Producer producer=new Producer(storage);
2 Thread thread1=new Thread(producer);

14.创建一个Consumer对象,并且用线程运行它。

1 Consumer consumer=new Consumer(storage);
2 Thread thread2=new Thread(consumer);

15.启动这两个线程。

1 thread2.start();
2 thread1.start();

它是如何工作的…

EventStorage 类的set()方法和get()方法是这个示例的关键。首先,set()方法检查storage属性是否有空闲空间。如果它满了,调用wait()方法等 待有空闲的空间。当其他线程调用notifyAll()方法,这个线程将被唤醒并且再次检查这个条件。这个notifyAll()方法并不保证线程会醒 来。这个过程是重复,直到storage有空闲空间,然后它可以生成一个新的事件并存储它。

get()方法的行为是相似的。首先,它检查storage是否有事件。如果EventStorage类是空的,调用wait()方法等待事件。当其他线程调用notifyAll()方法,这个线程将被唤醒并且再次检查这个条件直到storage有一些事件。

注释:在while循环中,你必须保持检查条件和调用wait()方法。你不能继续执行,直到这个条件为true。

不止这些…

synchronize关键字还有其他重要用法,请见其他指南中解释这个关键字使用的参见部分。

参见

在第2章,基本线程同步中在同步类中安排独立属性的食谱。 

时间: 2024-10-30 03:15:37

基本线程同步(四)在同步代码中使用条件的相关文章

JAVA之旅(十三)——线程的安全性,synchronized关键字,多线程同步代码块,同步函数,同步函数的锁是this

JAVA之旅(十三)--线程的安全性,synchronized关键字,多线程同步代码块,同步函数,同步函数的锁是this 我们继续上个篇幅接着讲线程的知识点 一.线程的安全性 当我们开启四个窗口(线程)把票陆陆续续的卖完了之后,我们要反思一下,这里面有没有安全隐患呢?在实际情况中,这种事情我们是必须要去考虑安全问题的,那我们模拟一下错误 package com.lgl.hellojava; import javax.security.auth.callback.TextInputCallback

进程、线程知识点总结和同步(消费者生产者,读者写者三类问题)、互斥、异步、并发、并行、死锁、活锁的总结

进程和程序: 进程:是个动态的概念,指的是一个静态的程序对某个数据集的一次运行活动,而程序是静态的概念,是由代码和数据组成的程序块而已. 进程5大特点:动态性,并发性,独立运行性,异步性,和结构化的特性. 在多道程序环境下,程序不能独立运行,操作系统所有的特征都是基于进程而体现的,只有进程可以在系统中运行,程序运行必须有进程才行.进程是操作系统里资源分配的基本单位,也是独立运行的基本单位,具有动态的特点,暂时出现的特点,而且一个进程产生之后,可以再次生成多个进程出来.也可以多个进程并发执行,也可

C#中的线程(四)高级话题

Keywords:C# 线程Source:http://www.albahari.com/threading/Author: Joe AlbahariTranslator: Swanky WuPublished: http://www.cnblogs.com/txw1958/Download:http://www.albahari.info/threading/threading.pdf   第四部分:高级话题   非阻止同步 早些时候,我们讨论了非常简单的赋值和 更新一个字段时需要使用同步的例

【OGG】RAC环境下配置OGG单向同步 (四)

[OGG]RAC环境下配置OGG单向同步 (四) 一.1  BLOG文档结构图       一.2  前言部分   一.2.1  导读 各位技术爱好者,看完本文后,你可以掌握如下的技能,也可以学到一些其它你所不知道的知识,~O(∩_∩)O~: ① RAC环境下配置OGG单向同步     注意:本篇BLOG中代码部分需要特别关注的地方我都用黄色背景和红色字体来表示,比如下边的例子中,thread 1的最大归档日志号为33,thread 2的最大归档日志号为43是需要特别关注的地方.   List

android-在安卓中开启一个新线程,发现如下代码中if块是DeadCode?为什么,如何解决?

问题描述 在安卓中开启一个新线程,发现如下代码中if块是DeadCode?为什么,如何解决? new Thread(){ public void run(){ try { sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } while(hasData < 100){ progressStatus = progressStatus + (i

C#中,怎么将一个窗体中已经改变的全局变量同步到另一个窗体中,将其赋值给局部变量

问题描述 C#中,怎么将一个窗体中已经改变的全局变量同步到另一个窗体中,将其赋值给局部变量 在全局变量定义的窗体中我定义了GlobalVariable.dianshu2和GlobalVariable.chishu2变量,然后在A窗体中给这两个全局变量都赋了值,在B窗体的一开始写了 static int dianshu= GlobalVariable.dianshu2; static int chishu= GlobalVariable.chishu2; double[,] m_X_ChiXian

android同步源码到过程中失败了是不是要从头再来?

问题描述 android同步源码到过程中失败了是不是要从头再来? 用repo工具同步android源码,在过程中失败了是不是要从头再来? 解决方案 是的,看下是不是网络有问题

作业-SQL 用JOB同步数据在数据库1中可以修改内容,但是发布到数据库2中不起作用,但是显示运行成功

问题描述 SQL 用JOB同步数据在数据库1中可以修改内容,但是发布到数据库2中不起作用,但是显示运行成功 同一个JOB发布到数据库2里面,运行之后看日志显示运行成功,但是没有修改数据成功,这是为什么?在数据库1中运行是可以的

main-java 子线程结束结束 内部子线程也结束 具体看代码注释

问题描述 java 子线程结束结束 内部子线程也结束 具体看代码注释 import java.util.ArrayList; import org.junit.Test; /** 代码很简单的 就是起了一个线程 然后在一个list里面放了1000个数字 然后打印以下 但是test方法 用junit起的 不能完全打印 而main可以 最终是想要使得这个test方法能和main的效果一样 * */ public class TestSub { @Test public void test() { /