juc05--线程通信

2023-10-20 10:05:36 浏览数 (1)

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

概述

核心:利用共享对象实现通信,这里的通信不是指传值,而是发送信号。 目的:就是让线程间具有互相发送信号通信的能力。 而且,线程通信可以实现,一个线程可以等待来自其他线程的信号。 举个例子,一个线程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 是对象的自有方法,一般在使用时会想当然的就调用,而忽略了要先拿到锁的前提。

0 人点赞