1. 生产者与消费者
生产者与消费者是个很好的线程通信的例子,生产者在一个循环中不断生产共享数据,而消费者则不断消费生产者生产的共享数据。程序必须保证有共享数据,如果没有,消费者必须等待生产新的共享数据。两者之间的数据关系如下:
1) 生产者生产前,如果共享数据没有被消费,则生产等待;生产者生产后,通知消费者消费。
2)消费者消费前,如果共享数据已经被消费完,则消费者等待;消费者消费后,通知生产者生产。
为了解决生产者和消费者的矛盾,引入了等待/通知(wait/notify)机制。
class Producer extends Thread { Queue q; Producer(Queue q) { this.q = q; } public void run() { for (int i = 1; i < 5; i++) { q.put(i); } } } class Consumer extends Thread { Queue q; // 声明队列q Consumer(Queue q){ this.q = q; // 队列q初始化 } public void run() { while (true) {// 循环消费元素 q.get(); // 获取队列中的元素 } } }
Producer 是一个生产者类,该生产者类提供一个以共享队列作为参数的构造方法,它的run 方法循环产生新的元素,并将元素添加于共享队列;Consumer 是一个消费者类,该消费者类提供一个以共享队列作为参数的构造方法,它的 run 方法循环消费元素,并将元素从共享队列删除。
2.共享队列
共享队列类是用于保存生产者生产、消费者消费的共享数据。共享队列有两个域:value(元素的数目)、isEmpty(队列的状态)。共享队列提供了put和 get 两个方法。
class Queue { int value = 0; // 声明,并初始化整数类型数据域value boolean isEmpty = true; // 声明,并初始化布尔类型数据域isEmpty,用于判断队列的状态 // 生产者生产方法 public synchronized void put(int v) { // 如果共享数据没有被消费,则生产者等待 if (!isEmpty) { try { System.out.println("生产者等待"); wait(); // 进入等待状态 } catch (Exception e) // 捕获异常 { e.printStackTrace(); // 异常信息输出 } } value += v; // value值加v isEmpty = false; // isEmpty赋值为false System.out.println("生产者共生产数量:" + v); notify(); } public synchronized int get() { if (isEmpty) { try { System.out.println("消费者等待"); wait(); } catch (Exception e) { e.printStackTrace(); } } value--; if (value < 1) { isEmpty = true; } System.out.println("消费者消费一个,剩余:" + value); notify(); return value; } }
生产者调用put方法生产共享数据,如果共享数据不为空,生产者线程进入等待状态;否则将生成新的数据,然后调用notify方法唤醒消费者线程进行消费;
消费者调用get方法消费共享数据,如果共享数据为空,消费者进入等待状态,否则将消费共享数据,然后提调用notify方法唤醒生产者线程进行生产。
3. 运行生产者与消费者
下面是生产者与消费者程序的主程序。
public class ThreadCommunication { public static void main(String[] args) { Queue q = new Queue(); Producer p = new Producer(q); Consumer c = new Consumer(q); c.start(); p.start(); } }
注意:考虑到程序的安全性,多数情况下使用 notifiAll(),除非明确可以知道唤醒哪一个线程。wait方法调用的前提条件是当前线程获取了这个对象的锁,也就是说 wait方法必须放在同步块或同步方法中。
为了保证数据安全使用 synchronized同步机制,当线程进入堵塞状态(不可运行状态和等待状态)时,其他线程无法访问那个加锁对象(除非同步锁被解除),所以
一个线程会一直处于等待另一个对象的状态,而另一个对象又会处于等待下一个对象的状态,以此类推,这个线程“等待”状态链会发生很糟糕的情形,即封闭环状态(也就是说最后那个对象在等待第一个对象的锁)。此时,所有的线程都陷入毫无止境的等待状态中,无法继续运行,这种情况就称为“死锁”。虽然这种情况发生的概率很小,一旦出现,程序的调试变得困难而且查错也是一件很麻烦的事情。
下面举一个死锁的例子。
public class ThreadLocked implements Runnable { public static boolean flag = true; // 起一个标志作用 private static Object A = new Object(); // 声明,并初始化静态Object数据域A private static Object B = new Object(); // 声明,并初始化静态Object数据域B public static void main(String[] args) throws InterruptedException { Runnable r1 = new ThreadLocked(); // 创建,并初始化ThreadLocked对象r1 Thread t1 = new Thread(r1); // 创建线程t1 Runnable r2 = new ThreadLocked(); // 创建,并初始化ThreadLocked对象r2 Thread t2 = new Thread(r2); // 创建线程t2 t1.start(); // 启动线程t1 t2.start(); // 启动线程t2 } public void AccessA() { flag = false; // 初始化域flag // 同步代码快 synchronized (A) { // 声明同步块,给对象A加锁 System.out.println("线程t1 : 我得到了A的锁"); // 输出字符串信息 try { // 让当前线程睡眠,从而让另外一个线程可以先得到对象B的锁 Thread.sleep(1000); // 休眠 } catch (InterruptedException e) { // 捕获异常 e.printStackTrace(); // 异常信息输出 } System.out.println("线程t1 : 我还想要得到B的锁"); // 在得到A锁之后,又想得到B的锁 // 同步块内部嵌套同步块 synchronized (B) { // 声明内部嵌套同步块,指定对象B的锁 System.out.println("线程t1 : 我得到了B的锁"); // 输出字符串信息 } } } public void AccessB() { flag = true; // 修改flag的值 // 同步代码块 synchronized (B) { // 指定同步块,给B加锁 System.out.println("线程t2 : 我得到了B的锁"); // 输出字符串信息 try { // 让当前线程睡眠,从而让另外一个线程可以先得到对象A的锁 Thread.sleep(1000); // 休眠 } catch (InterruptedException e) { // 捕获异常InterruptedException e.printStackTrace(); // 异常信息输出 } System.out.println("线程t2 : 我还想要得到A的锁"); // 字符串信息输出 // 在得到B锁之后,又想得到A的锁 // 同步块内部嵌套内部快 synchronized (A) { // 指定同步块,给A加锁 System.out.println("线程t2 : 我得到了A的锁"); // 输出字符串信息 } } } public void run() { if (flag){ // 当flag为true,执行下面语句 AccessA(); // 调用AccessA方法 } else { AccessB(); // 调用AccessB方法 } } }
程序 ThreadLocked.java中创建了两个线程 t1 和 t2,并且声明两个方法:AccessA和 AccessB。在运行过程中,线程t1 先获得了 A 的锁,然后又要求获得 B 的锁;而 t2
先获得B 的锁,然后又要求获得 A的锁,此时便进入了无休止的相互等待状态,即死锁。
Java 语言本身并没有提供防止死锁的具体方法,但是在具体程序设计时必须要谨慎,以防止出现死锁现象。通常在程序设计中应注意,不要使用 stop()、suspend()、resume()以及 destroy()方法。 stop()方法不安全,它会解除由该线程获得的所有对象锁,而且可能使对象处于不连贯状态,如果其他线程此时访问对象,而导致的错误很难检查出来。suspend()/resume ()方法也极不安全,调用 suspend()方法时,线程会停下来,但是该线程并没有放弃对象的锁,导致其他线程并不能获得对象锁。调用destroy()会强制终止线程,但是该线程也不会释放对象锁。
相关推荐
"多线程之间的线程通信"是确保多个线程协同工作、避免数据不一致性和提高程序效率的关键概念。在本话题中,我们将深入探讨线程通信的原理、方法,以及潜在的危险。 首先,线程通信是指在一个进程中,不同的线程之间...
在Java编程中,多线程通信是一个重要的概念,特别是在并发编程中。`ThreadDemo`示例可能演示了如何在不同的线程之间有效地传递信息。线程通信是解决多个执行流同步和协作的关键,确保数据的一致性和正确性。以下是...
在Java编程中,多线程通信是一个至关重要的概念,特别是在并发编程中,它涉及到线程间的协作和数据共享。线程通信安全问题是指在多线程环境下,如何保证多个线程对共享资源进行访问时的正确性和一致性。在这个场景下...
在Java编程中,多线程通信是一个至关重要的概念,特别是在设计高效的并发应用程序时。这个"Java线程通信示例源代码"很可能包含了演示如何在不同线程之间共享数据和协调执行顺序的实例。线程通信主要涉及两个核心概念...
在计算机科学中,进程线程通信、线程同步与异步以及进程间的通信是操作系统核心概念,对于理解和优化多任务并行处理至关重要。这些概念在软件开发,尤其是并发编程领域中占据着举足轻重的地位。 首先,让我们来探讨...
在本文中,我们将深入探讨如何使用Visual Studio 2017和C++来实现TCP套接字的多线程通信。TCP(传输控制协议)是一种面向连接、可靠的、基于字节流的通信协议,广泛应用于互联网上的各种服务。多线程技术则允许我们...
在C++编程中,多线程通信是并发执行任务时必不可少的一个环节,它涉及到线程间的同步和数据共享。在本篇文章中,我们将深入探讨如何在C++中实现多线程通信,以及相关的同步机制和数据交换策略。 一、线程创建与管理...
在计算机科学领域,尤其是软件开发中,多线程通信是一个重要的概念,特别是在处理并发任务和优化性能时。本文将深入探讨计算机网络中的多线程通信,以Java编程语言为例,结合"MT_WebServer"这一文件,来阐述如何实现...
多线程通信和等待机制 多线程通信和等待机制是多线程编程中一个重要的概念,它们都是基于线程之间的同步和协作来实现的。其中,wait()和notify()方法是Java语言中实现多线程通信和等待机制的两个核心方法。 wait()...
线程通信:使用消息实现线程通信,一个了解多线程与消息通信的例子,以下是实现的主要代码: LRESULT CThreadCommunicationDlg::OnDisplayResult(WPARAM wParam,LPARAM lParam) { int nResult = (int)wParam; ...
然而,多线程编程也带来了数据同步和线程通信的问题,以防止数据冲突和竞态条件。本教程将深入探讨四种常见的线程同步机制:事件对象、信号量、互斥量以及临界区,帮助开发者理解和掌握如何在VC++中安全地实现多线程...
Linux下C开发之线程通信 在Linux平台下,线程通信是C开发中一个非常重要的方面。本文将详细介绍Linux下C开发之线程通信的相关知识点,包括线程概念、线程创建、线程控制、线程通信和线程同步等。 一、线程概念 在...
线程通信是多线程编程中的一个重要概念,它是指在并发执行的多个线程之间交换信息的方式。在Java等编程语言中,线程通信通常用于解决共享数据的问题,确保线程间的协作和同步,防止数据竞争和死锁等问题。本资料“用...
在Java编程中,多线程通信是构建高效并发应用程序的关键技术。服务器多线程通信尤其重要,因为它允许服务器同时处理多个客户端请求,提高系统资源利用率并优化响应时间。本篇文章将深入探讨Java中的多线程通信,以及...
在IT领域,线程通信是多线程编程中的核心概念,特别是在复杂的系统设计中,如图书管理系统。线程通信指的是不同线程之间交换信息或同步执行的过程,以确保数据的一致性和程序的正确运行。本篇文章将深入探讨线程通信...
在Android应用开发中,线程通信是至关重要的一个环节,它涉及到UI线程与工作线程之间的数据交换和控制流程。本教程将深入探讨Android线程通信的基本概念、常用方法以及如何通过Demo来实践这些技术。 一、Android...
在多线程编程中,线程通信是一个至关重要的概念,特别是在并发执行任务时,确保不同线程间的协作和数据同步。本教程将聚焦于利用事件对象进行线程间的通信,这是实现多线程同步的一种常见方法。 事件对象,通常称为...
在VC++编程环境中,线程通信是多线程应用程序中不可或缺的一部分,特别是在需要同步或定时操作的场景下。本文将深入探讨如何利用VC++实现线程间的通信,并以计时器为例来阐述这一过程。 首先,理解线程通信的基本...
Socket多线程通信是网络编程中的重要组成部分,它允许服务器端和客户端进行高效的数据交互。在实际应用中,如在线聊天、文件传输等场景,往往需要用到多线程来提高并发处理能力,使得服务端可以同时处理多个客户端的...
线程通信是多线程编程中一个关键的组成部分,它确保线程间能有效地协同工作,避免竞争条件和死锁等问题。在Java中,线程通信主要依赖于共享内存和内置的等待/通知机制。 首先,线程通过共享对象通信是一种常见的...