Java多线程(五):线程之间的通讯

目录

1. 线程通讯

1.1 方法介绍

1.1.1 wait() 使用

1.1.2 notify 使用

1.1.3 notiyAll 使用

 1.1.4 注意事项

2. wait() VS wait(long timeout)

3. wait VS sleep

3.1 wait(0) 与 sleep(0) 的区别

3.2 wait 和 sleep 释放锁

3.3  wait和sleep区别(相同点&不同点)

4. 线程休眠和指定唤醒:LockSupport

4.1 park() 和 unpark()

 4.2 parkUntil(long) 

4.3 LockSupport VS wait


1. 线程通讯

        由于线程之间是抢占式执行的, 因此线程之间执行的先后顺序难以预知,但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序。举个例子:

Java多线程(五):线程之间的通讯_第1张图片

        球场上的每个运动员都是独立的 "执行流" , 可以认为是⼀个 "线程",而完成⼀个具体的进攻得分动作, 则需要多个运动员相互配合, 按照⼀定的顺序执行一定的动作,线程1 先 "传球" , 线程2 才能 "扣篮",从而得分。

1.1 方法介绍

完成这个协调工作(线程通讯),主要涉及到三个方法(这三个方法都需要配合 synchronized ⼀起使用):

  • wait() / wait(long timeout):让当前线程进入等待状态。
  • notify():唤醒当前对象上⼀个休眠的线程(随机)。
  • notifyAll():唤醒当前对象上的所有线程。

1.1.1 wait() 使用

wait 执行流程:

  • 使当前执行代码的线程进行等待. (把线程放到等待队列中)
  • 释放当前的锁
  • 满足一定条件时被唤醒, 重新尝试获取这个锁.

wait 结束等待的条件:

  • 其他线程调用该对象的 notify 方法.
  • wait 等待时间超时 (wait 方法提供⼀个带有 timeout 参数的版本, 来指定等待时间).
  • 其他线程调用该等待线程的 interrupted 方法, 导致 wait 抛出 InterruptedException 异常.

示例代码:

public class WaitDemo {
    public static void main(String[] args) {
        Object lock = new Object();

        Thread t1 = new Thread(() -> {
            System.out.println("线程1开始执行");
            try {
                synchronized (lock) {
                    System.out.println("线程1调用wait方法");
                    lock.wait();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程1执行完成");
        },"线程1");
        t1.start();
    }
}

执行结果:

Java多线程(五):线程之间的通讯_第2张图片

 线程1调用wait方法后,线程1进行等待,并未执行后面代码。

1.1.2 notify 使用

notify 方法是唤醒等待的线程:

  • 方法 notify() 也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。
  • 如果有多个线程等待,则有线程调度器随机挑选出⼀个呈 wait 状态的线程。(并没有 "先来后到")
  • notify() 方法后,当前线程不会马上释放该对象锁,要等到执行 notify() 方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁。

示例代码:

public class WaitDemo2 {
    public static void main(String[] args) {
        Object lock = new Object();

        Thread t1 = new Thread(() -> {
            System.out.println("线程1开始执行");
            try {
                synchronized (lock) {
                    System.out.println("线程1调用wait方法");
                    lock.wait();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程1执行完成");
        },"线程1");

        Thread t2 = new Thread(() -> {
            System.out.println("线程2开始执行");
            try {
                synchronized (lock) {
                    System.out.println("线程2调用wait方法");
                    lock.wait();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程2执行完成");
        },"线程2");

        Thread t3 = new Thread(() -> {
            System.out.println("线程3开始执行");
            try {
                synchronized (lock) {
                    System.out.println("线程3调用wait方法");
                    lock.wait();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程3执行完成");
        },"线程3");
        t1.start();
        t2.start();
        t3.start();

        // 唤醒lock对象上休眠的线程(随机一个)
        Thread t4 = new Thread(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程4开始执行");
            synchronized (lock) {
                lock.notify();
                System.out.println("线程4执行了唤醒操作");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程4 synchronized 执行完成");
            }
        },"线程4");
        t4.start();
    }
}

运行结果:

Java多线程(五):线程之间的通讯_第3张图片 Java多线程(五):线程之间的通讯_第4张图片

         观察两次运行结果可以发现:在 notify() 方法后,当前线程不会马上释放该对象锁,要等到执行 notify() 方法的线程将程序执行完才会释放对象锁,有三个等待的线程,每次唤醒的也是随机一个线程。

1.1.3 notiyAll 使用

        notify() 方法只是唤醒某⼀个等待线程. 使用 notifyAll() 方法可以⼀次唤醒所有的等待线程。

示例代码:

public class WaitDemo3 {
    public static void main(String[] args) {
        Object lock = new Object();
        Object lock2 = new Object();

        Thread t1 = new Thread(() -> {
            System.out.println("线程1开始执行");
            try {
                synchronized (lock) {
                    System.out.println("线程1调用wait方法");
                    lock.wait();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程1执行完成");
        },"线程1");

        Thread t2 = new Thread(() -> {
            System.out.println("线程2开始执行");
            try {
                synchronized (lock) {
                    System.out.println("线程2调用wait方法");
                    lock.wait();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程2执行完成");
        },"线程2");

        Thread t3 = new Thread(() -> {
            System.out.println("线程3开始执行");
            try {
                synchronized (lock2) {
                    System.out.println("线程3调用wait方法");
                    lock2.wait();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程3执行完成");
        },"线程3");
        t1.start();
        t2.start();
        t3.start();

        // 唤醒lock对象上休眠的线程(随机一个)
        Thread t4 = new Thread(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程4开始执行");
            synchronized (lock) {
                lock.notifyAll();
                System.out.println("线程4执行了唤醒操作");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程4 synchronized 执行完成");
            }
        },"线程4");
        t4.start();
    }
}

运行结果:

Java多线程(五):线程之间的通讯_第5张图片

可以观察到,notifyAll() 并不是唤醒所有 wait 等待的线程,而是唤醒当前对象(lock)处于 wait 等待的所有线程(线程1、线程2)。

 1.1.4 注意事项

  1. wait/notify/notifyAll 必须配合 synchronized 一起使用;
  2. wait 和 synchronized 必须使用同⼀把锁,不然会报错;
  3. 当调用了 notify/notifyAll 之后,程序并不会立即恢复执行,而是尝试获取锁,只有获取到锁之后才能继续执行;
  4. notifyAll 并不是唤醒所有 wait 的对象,而是唤醒当前对象所有 wait 的对象。

2. wait() VS wait(long timeout)

示例代码:

public class WaitDemo4 {
    public static void main(String[] args) {
        Object lock = new Object();
        Object lock2 = new Object();

        new Thread(() -> {
            System.out.println("线程1开始执行");
            synchronized (lock) {
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程1执行完成");
            }
        }, "无参wait线程").start();

        new Thread(() -> {
            System.out.println("线程2开始执行 " + LocalDateTime.now());
            synchronized (lock2) {
                try {
                    lock2.wait(5 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程2执行完成 " + LocalDateTime.now());
            }
        }, "有参wait线程").start();
    }
}

运行结果:

Java多线程(五):线程之间的通讯_第6张图片

 结论:

  • wait(long timeout) :当线程超过设定时间后,会自动恢复执行;wait() 无限期等待。
  • 使用无参的 wait() 方法,线程会进入 WAITING,使用有参的 wait() 方法,线程会进入 TIMED_WAITING.(可使用jconsole观察)
    Java多线程(五):线程之间的通讯_第7张图片Java多线程(五):线程之间的通讯_第8张图片

3. wait VS sleep

3.1 wait(0) 与 sleep(0) 的区别

public class WaitSleepDemo7 {
    public static void main(String[] args) {
        Object lock = new Object();

        Thread t1 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程1开始执行");
                try {
                    lock.wait(0);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程1结束执行");
            }
        }, "wait(0)");
        t1.start();

        Thread t2 = new Thread(() -> {
            System.out.println("线程2开始执行");
            try {
                Thread.sleep(0);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程2结束执行");
        }, "sleep(0)");
        t2.start();
    }
}

 运行结果:

Java多线程(五):线程之间的通讯_第9张图片

Thread.sleep(0) 表示重新触发一次 CPU 竞争;而 wait(0) 表示无期限的等待,直到有线程唤醒它为止。 

3.2 wait 和 sleep 释放锁

public class WaitSleepDemo8 {
    public static void main(String[] args) {
        Object lock = new Object();
        Object lock2 = new Object();

        Thread t1 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程1开始执行");
                try {
                    lock.wait(3 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程1结束执行");
            }
        }, "wait");
        t1.start();

        Thread t2 = new Thread(() -> {
            synchronized (lock2) {
                System.out.println("线程2开始执行");
                try {
                    Thread.sleep(3 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程2结束执行");
            }
        }, "sleep");
        t2.start();

        // 创建 2 个线程,先让线程休眠 1s 之后,尝试获取,看能不能获取到锁
        // 如果可以获取到锁,说明休眠时线程是释放锁的,而如果获取不到锁,说明是不释放锁
        Thread t3 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("尝试获取wait方法的锁");
            synchronized (lock) {
                System.out.println("成功获取wait方法的锁");
            }
        });
        t3.start();

        Thread t4 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("尝试获取sleep方法的锁");
            synchronized (lock2) {
                System.out.println("成功获取sleep方法的锁");
            }
        });
        t4.start();
    }
}

运行结果:

Java多线程(五):线程之间的通讯_第10张图片

wait方法在执行的时候都会释放锁,而sleep不会释放锁。

3.3  wait和sleep区别(相同点&不同点)

相同点:

  1. 都可以让线程休眠;
  2. 都可以响应 interrupt 的响应。

不同点:

  1. wait 必须在 synchronized 中使⽤,而 sleep 却不⽤;
  2. sleep 是 Thread 的⽅法,而 wait 是 Object 的⽅法;
  3. sleep 不释放锁,wait 释放锁;
  4. sleep 有明确的终止等待时间,而 wait 有可能无限期的等待下去;
  5. sleep 和 wait 产生的线程状态是不同的,sleep 是 TIMED_WAITING 状态,而 wait 是 WAITING 状态。

4. 线程休眠和指定唤醒:LockSupport

4.1 park()unpark()

使用 LockSupport 也可以使线程休眠和唤醒,它包含两个主要的方法: 

  • LockSupport.park():休眠当前线程。
  • LockSupport.unpark(线程对象):唤醒某⼀个指定的线程。

注意:LockSupport ⽆需配和 synchronized 使用。

示例代码:

public class LockSupportDemo1 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println("线程1开始执行");
            LockSupport.park();
            System.out.println("线程1结束执行");
        });
        t1.start();

        Thread t2 = new Thread(() -> {
            System.out.println("线程2开始执行");
            LockSupport.park();
            System.out.println("线程2结束执行");
        });
        t2.start();

        Thread t3 = new Thread(() -> {
            System.out.println("线程3开始执行");
            LockSupport.park();
            System.out.println("线程3结束执行");
        });
        t3.start();

        Thread.sleep(1000);
        System.out.println();
        LockSupport.unpark(t1);
        Thread.sleep(1000);
        LockSupport.unpark(t2);
        Thread.sleep(1000);
        LockSupport.unpark(t3);
    }
}

运行结果:

Java多线程(五):线程之间的通讯_第11张图片

 4.2 parkUntil(long) 

        LockSupport.parkUntil(long) 等待最⼤时间是⼀个固定时间.

示例代码:

public class LockSupportDemo2 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println("线程1开始执行:" + LocalDateTime.now());
            LockSupport.parkUntil(System.currentTimeMillis() + 3000);
            System.out.println("线程1结束执行:" + LocalDateTime.now());
        });
        t1.start();
    }
}

运行结果:

Java多线程(五):线程之间的通讯_第12张图片

 休眠 3s 后,自动恢复执行。

4.3 LockSupport VS wait

  1. LockSupport 使用时不需要加锁,而 wait 需要;
  2. LockSupport 不会抛出 Interrupt 的异常,而 wait 会;
  3. LockSupport 可以指定一个线程进醒唤醒,而 waitnotify 不行。

你可能感兴趣的