前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >juc05--线程通信

juc05--线程通信

作者头像
潇洒
发布2023-10-20 10:05:36
1510
发布2023-10-20 10:05:36
举报
文章被收录于专栏:石头岛

目的:就是让线程间具有互相发送信号通信的能力。

概述

核心:利用共享对象实现通信,这里的通信不是指传值,而是发送信号。 目的:就是让线程间具有互相发送信号通信的能力。 而且,线程通信可以实现,一个线程可以等待来自其他线程的信号。 举个例子,一个线程B可能正在等待来自线程A的信号,这个信号告诉线程B数据已经处理好了。

线程通信

开发中不免会遇到,需要所有子线程执行完毕通知主线程处理某些逻辑的场景。 或者是 线程A 在执行到某个条件通知 线程B 执行某个操作。 在java中,比较典型的就是:等待通知机制。

等待通知机制

等待通知模式是 Java 中比较经典的线程通信方式。 两个线程通过对同一对象调用等待 wait() 和通知 notify() 方法来进行通讯。 这种方式,有三个参与者:

  1. 阻塞线程 wait()
  2. 唤醒线程 notify()
  3. monitor锁

看个最简单的例子:

代码语言:javascript
复制
public class TestWaitNotify4 {

    /**
     * A 1, B 1, B 2, B 3, A 2, A 3
     */
    public static void main(String[] args) {
        Object lock = new Object();

        Thread A = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    System.out.println("A 1");
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println("A 2");
                    System.out.println("A 3");
                }

            }
        });

        Thread B = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    System.out.println("B 1");
                    System.out.println("B 2");
                    System.out.println("B 3");

                    lock.notify();
                }
            }
        });

        A.start();
        B.start();
    }
}

结果:

代码语言:javascript
复制
A 1
B 1
B 2
B 3
A 2
A 3

这个例子很简单,线程A 启动后,wait 自己,等待 线程B 唤醒自己。 这里 lock,就是一个 monitor锁,是不是奇怪,为什么需要一个 monitor锁,因为等待和唤醒必须是同一个锁,后面说。

线程通信方式

不同线程之间通过使用以下方法进行通信:

  1. wait(); 等待,该线程等待,并放弃执行权。
  2. notify(); 唤醒,唤醒正在等待中的其他线程。
  3. notifyAll(); 唤醒全部,推荐用这种 以上三个方法必须是同步线程中才能使用,锁对象才能使用。 只有同步才有锁的概念。

而上面三个方法是属于 Object 的方法,理由是: 因为这些方法在操作同步中线程时,都必须要标识它们所操作线程持有的锁,只有同一个锁上的被等待线程可以被同一个锁上的notify唤醒。 不可以对不同的锁中的线程进行唤醒。 也就是说等待唤醒必须是同一个锁,而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。

监视器对象

监视器对象,实际使用中,不止一种,不同场景有不同使用方式。 monitor锁是利用的对象的 monitor机制实现的,简单的说,对象的对象头存在隐藏字段,用来存储线程ID、锁标识、分代信息等,jvm 可以利用这个小存储进行线程状态的存储。

synchronized 方式

对象锁,就是创建一个中间对象,用来存储线程状态。 注间,这里方法使用 synchronized 进行修饰,synchronized 底层为 monitor锁。 使用 synchronized 和 synchronized(lock),这两个是等价的,锁的都是同一个 monitor锁。

代码语言:javascript
复制
// wait 和 notify 使用的是同一把锁
public Object lock = new Object();

public synchronized void notifySlef(){
  synchronized(lock) {
    lock.notify();
  }
}

public void waitSlef(){
  //注意,用的是 lock 这个对象来操作 wait
  synchronized(lock) {
    lock.wait();
  }
}

this 方式

this 锁,锁的当前线程自己,所以只会等到自己执行完成才会出这个方法。 这里就有个问题,如果 this.wait(),谁来唤醒自己,因为 this 只能是自己持有,别的线程根本不可能拿到这个锁。

代码语言:javascript
复制
public class Test1 {

  public static void main(String[] args) {
    Test2 testWait = new Test2();
    Thread t1 = new Thread(testWait);

    t1.start();
  }
}


class Test2 implements Runnable {

  public synchronized void notifySelf(){
    System.out.println("notify,thread: " + Thread.currentThread().getName());
    this.notify();
  }

  public synchronized void waitSelf(){
    try {
      System.out.println("wait,thread: " + Thread.currentThread().getName());
      this.wait();
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }


  @Override
  public void run() {
    System.out.println("current thread: " + Thread.currentThread().getName());
    waitSelf();
    notifySelf();
  }
}

结果: 只 wait 住自己,没有办法再唤醒自己,卡在 wait 这一步,程序也不会退出。

代码语言:javascript
复制
current thread: Thread-0
wait,thread: Thread-0

在线程通信中,使用 this.wait() 如果没有设置超时时间,就会一直被阻塞,因为没有线程可以拿到 this 锁。

类锁方式

这种方式也是对象没,每个类都会对就有一个 Class 对象,实际上就是锁的该类的 Class 对象。

代码语言:javascript
复制
public static synchronized void notifyClass() {
  Test.class.notify();
}

public void waitClass() {
  synchronized (Test.class) {
    Test.class.wait();
  }
 }

IllegalMonitorStateException 异常

异常代码演示

代码语言:javascript
复制
public class WaitTest {

    public void testWait(){
        System.out.println("Start-----");
        try {
            wait(1000);  //没有一个锁对象,所以报错!!!
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("End-------");
    }

    public static void main(String[] args) {
        final WaitTest test = new WaitTest();
        new Thread(new Runnable() {
            @Override
            public void run() {
                test.testWait();
            }
        }).start();
    }
}

错误提示

代码语言:javascript
复制
Exception in thread "Thread-0" Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
Thread-1
    at java.lang.Object.wait(Native Method)

错误的主要原因为: 违法的监控状态异常。当某个线程试图等待一个自己并不拥有的对象(O)的监控器或者通知其他线程等待该对象(O)的监控器时,抛出该异常。

大白话就是

当前线程必须持有一个当前线程的锁,才能使用 wait。 当前线程并没有持有一个锁,就来调 wait 方法,直接抛异常。 要使用 wait 必须拥有该对象的锁!!! 详细说明在 wait() 方法的JDK注释中有详细说明。

正确写法

代码语言:javascript
复制
public class WaitTest {

    public static void main(String[] args) {
        final WaitTest test = new WaitTest();
        new Thread(new Runnable() {
            @Override
            public void run() {
                test.testWait();
            }
        }).start();
    }


    public synchronized void testWait(){
        //增加Synchronized关键字
        System.out.println("Start-----");
        try {
            wait(1000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("End-------");
    }

}

总结

waitnotify 一般在使用时,容易搞混的就是 IllegalMonitorStateException 异常。 主要是容易忽略需要加锁这件事,因为 waitnotify 是对象的自有方法,一般在使用时会想当然的就调用,而忽略了要先拿到锁的前提。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2014-09-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概述
  • 线程通信
    • 等待通知机制
      • 线程通信方式
      • 监视器对象
        • synchronized 方式
          • this 方式
            • 类锁方式
            • IllegalMonitorStateException 异常
              • 异常代码演示
                • 大白话就是
                  • 正确写法
                  • 总结
                  相关产品与服务
                  对象存储
                  对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档