JUC之线程通信(第二部分)

115 阅读2分钟

Condition的使用

「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」。

与synchronized再做一个比较:

Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法wait notify和notifyAll的使用;

使用Lock condition接口实现买票:

 package com.JUC;
 ​
 import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 ​
 class shareDemo {
     private Lock lock = new ReentrantLock();
     private Condition condition = lock.newCondition();
     private int number = 0;
 ​
     public void inc() throws InterruptedException {
         lock.lock();
 ​
         try{
             while(number != 0){
                 condition.await();
             }
             number++;
             System.out.println(Thread.currentThread().getName()+"::"+number);
             /**
              * 唤醒多有等待的线程
              */
             condition.signalAll();
 ​
         }finally {
             lock.unlock();
         }
     }
     public void sub(){
         lock.lock();
 ​
         try{
             while(number != 1){
                 condition.await();
             }
             number--;
             System.out.println(Thread.currentThread().getName()+"::"+number);
             /**
              * 唤醒多有等待的线程
              */
             condition.signalAll();
 ​
         } catch (InterruptedException e) {
             e.printStackTrace();
         } finally {
             lock.unlock();
         }
     }
 }
 ​
 public class ConditionLocal {
     public static void main(String[] args) {
         shareDemo share = new shareDemo();
         new Thread(()->{
             for (int i = 0; i < 10; i++) {
                 try {
                     share.inc();
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
 ​
         },"AAA").start();
         new Thread(()->{
             for (int i = 0; i < 10; i++) {
                 share.sub();
             }
 ​
         },"BBB").start();
         new Thread(()->{
             for (int i = 0; i < 10; i++) {
                 try {
                     share.inc();
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
             }
 ​
         },"CCC").start();
         new Thread(()->{
             for (int i = 0; i < 10; i++) {
                 share.sub();
             }
 ​
         },"DDD").start();
     }
 }

在书籍4.3.1-4.3.3对应的其实是该文章中线程通信的例子。

管道输入/输出流:

线程间通信的方式还有管道输入/输出流:与文件的输入输出不同的是,它主要用于线程间的数据传输,传输的媒介是内存;

以下是书中的内容:

管道输入/输出流主要包括了如下4种具体实现:PipedOutputStream、PipedInputStream、 PipedReader和PipedWriter,前两种面向字节,而后两种面向字符。

实现例子:

对于Piped类型的流,必须先要进行绑定,也就是调用connect()方法,如果没有将输入/输

出流绑定起来,对于该流的访问将会抛出异常

 package com.JUC;
 ​
 import java.io.IOException;
 import java.io.PipedReader;
 import java.io.PipedWriter;
 ​
 public class PipeInOut {
     public static void main(String[] args) throws IOException {
         PipedWriter out = new PipedWriter();
         PipedReader in = new PipedReader();
         //将输入流和输出流进行连接,否则会出现IO错误
         out.connect(in);
         //创建print线程来接收Main中的输入
         Thread thread = new Thread(new Print(in),"PrintThread");
         //开启该线程,开始接收数据
         thread.start();
         int receive = 0;
         try {
             //接收输入的数据并赋值
             while((receive = System.in.read()) != -1){
                 out.write(receive);
             }
         }finally{
             out.close();
         }
 ​
     }
 ​
     static class Print implements Runnable {
         private  PipedReader in;
         public Print(PipedReader in) {
             this.in = in;
         }
 ​
         @Override
         public void run() {
             int receive = 0;
             try {
                 while((receive = in.read()) != -1){
                     System.out.print((char) receive);
                 }
             }catch(IOException ex){
 ​
             }
         }
     }
 }

image-20211229193144160

Thread.join()的使用

书中的定义:其含义是:当前线程A等待thread线程终止之后才从thread.join()返回;感觉不太好理解;

Java 7 Concurrency Cookbook

是主线程等待子线程的终止。也就是说主线程的代码块中,如果碰到了t.join()方法,此时主线程需要等待(阻塞),等待子线程结束了(Waits for this thread to die.),才能继续执行t.join()之后的代码块。

例子:

 package com.JUC;
 ​
 import java.util.concurrent.TimeUnit;
 ​
 public class Join {
     public static void main(String[] args) throws InterruptedException {
         Thread previous = Thread.currentThread();
         for (int i = 0; i < 10; i++) {
             // 每个线程拥有前一个线程的引用,需要等待前一个线程终止,才能从等待中返回
             Thread thread = new Thread(new Domino(previous), String.valueOf(i));
             thread.start();
             previous = thread;
         }
         TimeUnit.SECONDS.sleep(5);
         //主线程
         System.out.println(Thread.currentThread().getName()+"--terminate.");
     }
 ​
     static class Domino implements Runnable {
         private Thread thread;
         public Domino(Thread thread) {
             this.thread = thread;
         }
 ​
         @Override
         public void run() {
             try {
                 thread.join();
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             //子线程
             System.out.println(Thread.currentThread().getName()+"---Terminate.");
         }
     }
 }

每个线程终止的前提是前驱线程的终止,每个线程等待前驱线程终止后,才从join()方法返回;

我们查看下join方法的源码可以发现其中也是用的synchronized修饰的;

 public final void join() throws InterruptedException {
     join(0);
 }
 public final synchronized void join(long millis)
     throws InterruptedException {
     long base = System.currentTimeMillis();
     long now = 0;
 ​
     if (millis < 0) {
         throw new IllegalArgumentException("timeout value is negative");
     }
 ​
     if (millis == 0) {
         while (isAlive()) {
             wait(0);
         }
     } else {
         while (isAlive()) {
             long delay = millis - now;
             if (delay <= 0) {
                 break;
             }
             wait(delay);
             now = System.currentTimeMillis() - base;
         }
     }
 }

关于书中的4.3.6ThreadLocal的使用可以看下以前写的文章:点击进入

参考:

《JUC并发编程的艺术》

《【尚硅谷】大厂必备技术之JUC并发编程》