美文网首页
Java线程通信

Java线程通信

作者: JuneWool | 来源:发表于2019-10-13 23:15 被阅读0次

线程通信

线程通信指的是多个线程在运行的期间,相互之间的数据交互协作。

1.通信方式

实现多个线程直接的协作,涉及到的通信方式主要四类。
1)文件共享
2)网络共享
3)共享变量
4)JDK提供的线程协调API

1.文件共享
 线程A写文件,线程B读取文件达到线程协作。
2.网络共享
 线程A发送数据,线程B接受数据达到线程协作。
3.共享变量
 利用内存的公共区域,共享变量。线程A修改变量,线程B读取变量达到线程协作。

以上3种方式都是比较触及的,我们主要链接JDK给我提供的API

2.线程协作 JDK API

生产者消费者模型是线程协作的典型场景(线程阻塞、线程唤醒),我们以一个示例去理解
示例:线程A提车,没有车,则不执行。线程B有车了,通知线程A继续执行

2.1 suspend/resume

suspend挂起线程,resume恢复线程执行。这两个API是Thread提供的。
示例:

public static Object benz = null;
public void suspendResumeTest() throws Exception {
        // 启动线程
        Thread consumerThread = new Thread(() -> {
            if (benz == null) { // 如果没奔驰,则进入等待
                System.out.println("1、进入等待");
                Thread.currentThread().suspend();
            }
            System.out.println("2、提到车,回家");
        });
        consumerThread.start();
        // 3秒之后,拉来一辆奔驰
        Thread.sleep(3000L);
        benz = new Object();
        consumerThread.resume();
        System.out.println("3、通知消费者");
    }

suspend/resume虽然是Thread提供的,因为很容易写出死锁的代码。 所以被弃用了。
死锁示例1:在写同步代码的时候容易出现:suspend在挂起之后并不会释放锁

public void suspendResumeDeadLockTest() throws Exception {
        // 启动线程
        Thread consumerThread = new Thread(() -> {
            if (benz == null) { // 如果没奔驰,则进入等待
                System.out.println("1、进入等待");
                // 当前线程拿到锁,然后挂起
                synchronized (this) {
                    Thread.currentThread().suspend();
                }
            }
            System.out.println("2、提到车,回家");
        });
        consumerThread.start();
        // 3秒之后,拉来一辆奔驰
        Thread.sleep(3000L);
        benz = new Object();
        // 争取到锁以后,再恢复consumerThread
        synchronized (this) {
            consumerThread.resume();
        }
        System.out.println("3、通知消费者");
    }

这种情况下消费者如果拿到锁,消费者就挂起了,生产者要通知消费者必须要抢到锁,但是因为消费者抢到锁之后挂起,并没有释放锁,生产者是抢不到锁,这就死锁了。
死锁示例2: API调用顺序:suspend在resume后执行死锁

public void suspendResumeDeadLockTest2() throws Exception {
        // 启动线程
        Thread consumerThread = new Thread(() -> {
            if (benz == null) {
                System.out.println("1、没奔驰,进入等待");
                try { // 为这个线程加上一点延时
                    Thread.sleep(5000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 这里的挂起执行在resume后面
                Thread.currentThread().suspend();
            }
            System.out.println("2、提到车,回家");
        });
        consumerThread.start();
        // 3秒之后,拉来一辆奔驰
        Thread.sleep(3000L);
        benz = new Object();
        consumerThread.resume();
        System.out.println("3、通知消费者");
        consumerThread.join();
    }

这种情况下suspend在resume后执行,线程恢复不了执行,死锁了。

2.2 wait/notify

这一对API只能由统一对象锁的持有者调用,也就是写在同步代码块里面,否则抛异常。
wait使当前线程等待,加入该对象的等待池,并释放锁。
notify/notifyAll唤醒一个或所有正在等待这个对象的线程
因为必须用在同步代码块里面, wait/notify针对锁的问题不存在了。但是顺序需要注意。
示例:

public void waitNotifyTest() throws Exception {
       // 启动线程
       new Thread(() -> {
           if (benz == null) { // 如果没奔驰,则进入等待
               synchronized (this) {
                   try {
                       System.out.println("1、进入等待");
                       this.wait();
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
               }
           }
           System.out.println("2、提到车,回家");
       }).start();
       // 3秒之后,拉来一辆奔驰
       Thread.sleep(3000L);
       benz = new Object();
       synchronized (this) {
           this.notifyAll();
           System.out.println("3、通知消费者");
       }
   }

注意:虽然wait会释放锁,但是对顺序有要求,如果wait在notify之调用,线程就永远处于WAITING状态了。
死锁示例:

public void waitNotifyDeadLockTest() throws Exception {
       // 启动线程
       new Thread(() -> {
           if (benz == null) { // 如果没奔驰,则进入等待
               try {
                   Thread.sleep(5000L);
               } catch (InterruptedException e1) {
                   e1.printStackTrace();
               }
               synchronized (this) {
                   try {
                       System.out.println("1、进入等待");
                       this.wait();
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
               }
           }
           System.out.println("2、提到车,回家");
       }).start();
       // 3秒之后,拉来一辆奔驰
       Thread.sleep(3000L);
       benz = new Object();
       synchronized (this) {
           this.notifyAll();
           System.out.println("3、通知消费者");
       }
   }

这种情况下wait在notify后执行,线程收不到通知,死锁了。

2.3 park/unpark

park等待许可,unpark提供许可令牌,让线程继续执行,park/unpark没有顺序要求。
多次unpark之后调用park,线程会直接执行,因为已经拿到许可令牌了。
许可令牌不会叠加,多次调用park,只有第一次会拿到许可,继续执行,后续的调用则进入等待。
示例:

public void parkUnparkTest() throws Exception {
        // 启动线程
        Thread consumerThread = new Thread(() -> {
            if (benz == null) { // 如果没奔驰,则进入等待
                System.out.println("1、进入等待");
                LockSupport.park();
            }
            System.out.println("2、提到车,回家");
        });
        consumerThread.start();
        // 3秒之后,拉来一辆奔驰
        Thread.sleep(3000L);
        benz = new Object();
        LockSupport.unpark(consumerThread);
        System.out.println("3、通知消费者");
    }

park/unpark不会释放锁,所以在同步代码块里面使用不当就容易死锁。
死锁示例:

public void parkUnparkDeadLockTest() throws Exception {
        // 启动线程
        Thread consumerThread = new Thread(() -> {
            if (benz == null) { // 如果没奔驰,则进入等待
                System.out.println("1、进入等待");
                // 当前线程拿到锁,然后挂起
                synchronized (this) {
                    LockSupport.park();
                }
            }
            System.out.println("2、提到车,回家");
        });
        consumerThread.start();
        // 3秒之后,拉来一辆奔驰
        Thread.sleep(3000L);
        benz = new Object();
        // 争取到锁以后,再恢复consumerThread
        synchronized (this) {
            LockSupport.unpark(consumerThread);
        }
        System.out.println("3、通知消费者");
    }

消费者挂起之后没有释放锁,生产者永远获取不到锁,死锁。

2.4 join

有人说join也是一种,其实join底层使用的wait/notify

结语

虽然都是些简单例子,但是我们通过这些例子去看正确的操作,还有死锁的情况,我可以在平时写这类代码的时候可以避免踩坑。弃用的suspend/resume就不要用了,wait/notify、park/unpark看场景需要使用。
第一次写博客,排版乱糟糟,有很多知识点还没完全讲到,讲得不够详细,多多谅解。迈出一步,那也是进步。

相关文章

  • 2 Java内存模型

    java中线程通信用的是共享内存模型,java线程之间的通信总是隐式进行,整个通信过程对程序员完全透明。 Java...

  • Java线程模型

    Java线程模型 本文将从线程类型、线程通信、线程调度三个方面分析Java中的线程模型。 什么是线程? 线程就是进...

  • Java多线程的使用通信和控制

    Java多线程的使用通信和控制 1. Java多线程的使用和通信 实现多线程有两种方式 1. 继承Thread父类...

  • 线程间通信剖析——Java进阶

    Java线程间通信剖析 本文将介绍常用的线程间通信工具CountDownLatch、CyclicBarrier和P...

  • Java多线程(2)

    Java多线程(2) 线程间的通信 线程间的通信又称为进程内通信,多个线程实现互斥访问共享资源时会互相发送信号或等...

  • java内存模型

    java内存模型基础 并发编程,两个关键问题:线程通信和线程同步通信机制:共享内存和消息传递 java并发采用共享...

  • Java 线程通信之 wait/notify 机制

    前言 Java 线程通信是将多个独立的线程个体进行关联处理,使得线程与线程之间能进行相互通信。比如线程 A 修改了...

  • 第二节、Handler消息机制

    一、为什么要设计handler Java 多线程通信 Java 中有很多种方法实现线程之间相互通信访问数据,大概先...

  • Java socket

    Java Socket实现基于TCP和UDP多线程通信Java Socket编程

  • Java线程简介

    本文将介绍Java线程的状态、线程的中断、线程间通信和线程的实现。 线程的状态 Java语言定义了6种不同的线程状...

网友评论

      本文标题:Java线程通信

      本文链接:https://www.haomeiwen.com/subject/ccflmctx.html