Java基础知识(14)- Java 多线程编程(三) | 线程间通信
线程在操作系统中是相互独立的,同一进程下相互独立的线程,如果不经过特殊的处理就不能成为一个整体来处理复杂的业务。
线程间的通信就是成为整体的必用方案之一,可以说,使线程间进行通信后,系统之间的交互性会更强大,在大大提高CPU利用率的同时,还会使程序员对个线程任务在处理的过程中进行更有效的把控与监督。
线程间的通信常用方式:while 循环轮询、wait/notify 机制、管道通信
1. while 循环轮询
while 循环轮询在开发过程中的应用是比较广泛的,通过 while 循环轮询来检测某一个条件是否成立。
实例:
1 import java.util.List; 2 import java.util.ArrayList; 3 4 public class App { 5 6 public static void main( String[] args ) { 7 8 final List testList = new ArrayList<String>(); 9 10 Thread threadWhile1 = new Thread(new Runnable() { 11 public void run() { 12 String name = Thread.currentThread().getName(); 13 try { 14 for (int i = 1; i <= 10; i++) { 15 testList.add("Element " + i); 16 System.out.println(name + ": add " + i + " elements"); 17 Thread.sleep(1000); 18 } 19 } catch (InterruptedException e) { 20 e.printStackTrace(); 21 } 22 } 23 }, "Thread While 1" ); 24 threadWhile1.start(); 25 26 Thread threadWhile2 = new Thread(new Runnable() { 27 public void run() { 28 String name = Thread.currentThread().getName(); 29 try { 30 while (true) { 31 if (testList.size() == 5) { 32 throw new InterruptedException(name + ": testList.size() == 5, exit"); 33 } 34 Thread.sleep(500); // 不调用sleep,无法退出 while 循环, 35 // 因为 testList.size() 不是 volatile 变量 36 } 37 } catch (InterruptedException e) { 38 //e.printStackTrace(); 39 System.out.println(e.getMessage()); 40 } 41 } 42 }, "Thread While 2" ); 43 threadWhile2.start(); 44 } 45 }
输出:
Thread While 1: add 1 elements
Thread While 1: add 2 elements
Thread While 1: add 3 elements
Thread While 1: add 4 elements
Thread While 1: add 5 elements
Thread While 2: testList.size() == 5, exit
Thread While 1: add 6 elements
Thread While 1: add 7 elements
Thread While 1: add 8 elements
Thread While 1: add 9 elements
Thread While 1: add 10 elements
以上实例中,线程 threadWhile1 添加数据到 testList,线程 threadWhile2 的 while 循环检测 (testList.size() == 5) 是否成立。
这种方式很浪费CPU资源,因为JVM调度器将CPU交给线程 threadWhile2 执行时,它没做啥“有用”的工作,只是在不断地测试某个条件是否成立。如果轮询的时间间隔很小,更浪费CPU资源;如果轮询的时间间隔很大,有可能会取不到想要得到的数据。
另外一个问题:
线程 threadWhile2 的 while 循环里 testList.size() 的可见性问题(可参考:Java基础知识(13)- Java 多线程编程(二) 的 "2. volatile 关键字")。
在 while 循环里加上 Thread.sleep(500) ,似乎解决了这个问题,但是 sleep() 的参数 500 是很随意的值,在不同环境的测试结果是不确定的。
实现多线程间通信,尽量避免使用未优化的 while 循环轮询机制。
2. wait/notify 机制
关键字 synchronized 可以将任何一个Object对象作为同步对象来看待,而Java为每个Object都实现了wait()和notify()方法,它们必须用在被synchronized同步的Object的临界区内。
wait()方法可以使调用该方法的线程释放共享资源的锁,然后从运行状态退出,进入等待队列,直到被再次唤醒。
notify()方法可以随机唤醒等待队列中等待同一共享资源的“一个”线程,并使该线程退出等待队列,进入可运行状态,也就是notify()方法仅通知“一个”线程。
notifyAll()方法可以使所有正在等待队列中等待统一共享资源的“全部”线程从等待状态退出,进入可运行状态。此时,优先级最高的那个线程最先执行,但也有可能是随机执行,因为这要取决于JVM虚拟机的实现。
实例:
1 import java.util.List; 2 import java.util.ArrayList; 3 4 public class App { 5 public static void main( String[] args ) { 6 7 final List testList = new ArrayList<String>(); 8 9 // wait/notify 机制 10 Thread threadWait1 = new Thread(new Runnable() { 11 public void run() { 12 String name = Thread.currentThread().getName(); 13 try { 14 synchronized (testList) { 15 if (testList.size() != 5) { 16 System.out.println(name + ": wait() begin " + System.currentTimeMillis()); 17 testList.wait(); 18 System.out.println(name + ": wait() end " + System.currentTimeMillis()); 19 } 20 } 21 } catch (InterruptedException e) { 22 e.printStackTrace(); 23 } 24 } 25 }, "Thread Wait 1" ); 26 threadWait1.start(); 27 28 Thread threadWait2 = new Thread(new Runnable() { 29 public void run() { 30 String name = Thread.currentThread().getName(); 31 try { 32 33 for (int i = 1; i <= 10; i++) { 34 testList.add("Element " + i); 35 36 synchronized (testList) { 37 if (testList.size() == 5) { 38 testList.notify(); 39 System.out.println(name + ": " + testList.size() + " elements, notify()"); 40 } else { 41 System.out.println(name + ": " + i + " elements"); 42 } 43 } 44 45 Thread.sleep(1000); 46 } 47 48 } catch (InterruptedException e) { 49 e.printStackTrace(); 50 } 51 } 52 }, "Thread Wait 2" ); 53 threadWait2.start(); 54 } 55 56 }
输出:
Thread Wait 1: wait() begin 1645871509404
Thread Wait 2: 1 elements
Thread Wait 2: 2 elements
Thread Wait 2: 3 elements
Thread Wait 2: 4 elements
Thread Wait 2: 5 elements, notify()
Thread Wait 1: wait() end 1645871513447
Thread Wait 2: 6 elements
Thread Wait 2: 7 elements
Thread Wait 2: 8 elements
Thread Wait 2: 9 elements
Thread Wait 2: 10 elements
以上实例中,使用 synchronized 将 testList 作为两个线程的同步对象。
线程 threadWait1 先运行,此时 testList.size() 等于 0, testList.wait() 被调用,线程 threadWait1 进入等待状态;
线程 threadWait2 的 testList.size() 等于 5 时,testList.notify() 被调用,线程 threadWait1 被唤醒;
*注意: 线程 threadWait2 上 synchronized (testList) {} 如果包在 for 循环之外,synchronized 保护下的 for循环会运行完之后,线程 threadWait1 的 synchronized 代码才会运行。
3. 管道通信
Java 语言提供了各种各样的输入/输出流 Stream ,使我们能够很方便地对数据进行操作,其中管道流是一种特殊的流,用于在不同线程间直接传送数据。一个线程发送数据到输出管道流,另一个线程从输入管道流中读取数据。
通过使用管道,实现不同线程间的通信,而无须借助于类似临时文件之类的东西。
管道流的类型:
(1) 字节流 PipedInputStream 和 PipedOutputStream
(2) 字符流 PipedReader 和 PipedWriter
PipedReader 和 PipedWriter,意为管道读写流。所谓管道,那就是有进有出,所以这也是它们跟其它流对象最显著的区别:PipedReader 和 PipedWriter 必须成对使用才有意义。
PipedWriter 扮演生产者的角色,将字符数据写入到管道;PipedReader 扮演消费者的角色,负责将数据从管道取出消费掉。
实例:
1 import java.util.List; 2 import java.util.ArrayList; 3 4 import java.io.PipedReader; 5 import java.io.PipedWriter; 6 import java.io.IOException; 7 8 public class App { 9 10 public static void main( String[] args ) { 11 12 final List testList = new ArrayList<String>(); 13 14 final PipedWriter pipedWriter = new PipedWriter(); 15 final PipedReader pipedReader = new PipedReader(); 16 17 try { 18 pipedReader.connect(pipedWriter); 19 } catch (IOException e) { 20 e.printStackTrace(); 21 } 22 23 // 管道通信 24 System.out.println("----------------- 管道通信 --------------------"); 25 Thread threadPipe1 = new Thread(new Runnable() { 26 public void run() { 27 String name = Thread.currentThread().getName(); 28 try { 29 30 System.out.println(name + ": PipedReader begin"); 31 String str = read(pipedReader); 32 33 if ("LIST_SIZE_5".equals(str)) { 34 System.out.println(name + ": PipedReader end, read() = " + str); 35 } 36 37 } catch (IOException e) { 38 e.printStackTrace(); 39 } 40 } 41 }, "Thread Pipe 1" ); 42 threadPipe1.start(); 43 44 Thread threadPipe2 = new Thread(new Runnable() { 45 public void run() { 46 String name = Thread.currentThread().getName(); 47 try { 48 49 for (int i = 1; i <= 10; i++) { 50 testList.add("Element " + i); 51 52 if (testList.size() == 5) { 53 54 write(pipedWriter, "LIST_SIZE_5"); 55 56 System.out.println(name + ": " + testList.size() + " elements, write('LIST_SIZE_5')"); 57 } else { 58 System.out.println(name + ": " + i + " elements"); 59 } 60 61 Thread.sleep(1000); 62 } 63 64 } catch (InterruptedException e) { 65 e.printStackTrace(); 66 } catch (IOException e) { 67 e.printStackTrace(); 68 } 69 } 70 }, "Thread Pipe 2" ); 71 threadPipe2.start(); 72 } 73 74 public static String read(PipedReader reader) throws IOException { 75 76 String strData = ""; 77 78 char[] byteArray = new char[50]; 79 int len = reader.read(byteArray); 80 81 while (len != -1) { 82 strData = new String(byteArray, 0, len); 83 len = reader.read(byteArray); 84 } 85 86 reader.close(); 87 return strData; 88 } 89 90 public static void write(PipedWriter writer, String strData) throws IOException { 91 92 writer.write(strData); 93 writer.close(); 94 } 95 96 }
输出:
Thread Pipe 1: PipedReader begin
Thread Pipe 2: 1 elements
Thread Pipe 2: 2 elements
Thread Pipe 2: 3 elements
Thread Pipe 2: 4 elements
Thread Pipe 2: 5 elements, write('LIST_SIZE_5')
Thread Pipe 1: PipedReader end, read() = LIST_SIZE_5
Thread Pipe 2: 6 elements
Thread Pipe 2: 7 elements
Thread Pipe 2: 8 elements
Thread Pipe 2: 9 elements
Thread Pipe 2: 10 elements
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
· .NET 10 首个预览版发布,跨平台开发与性能全面提升
· 《HelloGitHub》第 107 期
· 全程使用 AI 从 0 到 1 写了个小工具
· 从文本到图像:SSE 如何助力 AI 内容实时呈现?(Typescript篇)