第3章 线程间通信

等待/通知机制

线程于线程之间不是独立的个体,它们彼此之间可以相互通信与协作。

等待/通知机制的实现

方法 wait() 的作用是使当前执行代码的线程进行等待,wait() 方法使 Object 类的方法,该方法将当前线程置入“预执行队列中”,并且在 wait() 所执行代码处停止执行,知道接到通知或被中断为止。在调用 wait() 之前,线程必须获得该对象的对象级别锁。在执行 wait() 方法后,当前线程释放锁。
方法 notify() 也要在同步方法或同步块中调用,即在调用前,线程也必须获得该对象的对象级别锁。该方法用来通知那些可能等待该对象的对象锁的其他线程,如果由多个等待线程,则由多线程规划器随机挑选出一个呈 wait() 状态的线程,对其发出通知 notify,并使它等待获取该对象的对象锁。
wait 使线程停止运行,而 notify 使停止的线程继续运行。

public class MyThread1 extends Thread {
    private Object lock;

    public MyThread1(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        try {
            synchronized (lock){
                System.out.println("开始 wait time = " + System.currentTimeMillis());
                lock.wait();
                System.out.println("结束 wait time = " + System.currentTimeMillis());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class MyThread2 extends Thread {
    private Object lock;

    public MyThread2(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock){
            System.out.println("开始 notify time = " + System.currentTimeMillis());
            lock.notify();
            System.out.println("结束 notify time = " + System.currentTimeMillis());
        }
    }
}

public class Test {
    public static void main(String[] args) {
        try {
            Object lock = new Object();
            MyThread1 t1 = new MyThread1(lock);
            t1.start();
            Thread.sleep(3000);
            MyThread2 t2 = new MyThread2(lock);
            t2.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:
开始 wait time = 1543231514059
开始 notify time = 1543231517064
结束 notify time = 1543231517064
结束 wait time = 1543231517064

public class MyList {
    private static List list = new ArrayList();
    public static void add(){
        list.add("anyString");
    }
    public static int size(){
        return list.size();
    }
}

public class ThreadA extends Thread {
    private Object lock;

    public ThreadA(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        try {
            synchronized (lock){
                if(MyList.size() != 5){
                    System.out.println("wait begin " + System.currentTimeMillis());
                    lock.wait();
                    System.out.println("wait end " + System.currentTimeMillis());
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class ThreadB extends Thread {
    private Object lock;

    public ThreadB(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        try {
            synchronized (lock){
                for (int i = 0; i < 10; i++) {
                    MyList.add();
                    if(MyList.size() == 5){
                        //根据运行结果来看,执行notify以后,并不是立即释放锁,因为这个方法还在继续执行
                        lock.notify();
                        System.out.println("已发出通知!");
                    }
                    System.out.println("添加了 " + (i+1) + "个元素");
                    Thread.sleep(1000);
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class Run {
    public static void main(String[] args) {
        try {
            Object lock = new Object();
            ThreadA threadA = new ThreadA(lock);
            threadA.start();
            Thread.sleep(50);
            ThreadB threadB = new ThreadB(lock);
            threadB.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

关键字 synchronized 可以将任何一个 Object 对象作为同步对象,而 Java 为每个 Object 都实现了 wait() 和 notify() 方法。它们必须用在被 synchronized 同步的 Object 临界区内。通过调用 wait() 方法可以使处于临界区内的线程进入等待状态,同时释放被同步的对象锁。而 notify 操作可以唤醒一个因调用了 wait 操作而处于阻塞状态中的线程,使其进入就绪状态。
notify() 方法可以随机唤醒等待队列中等待同一个资源的一个线程,并使该线程退出等待队列,进入可运行状态,也就是 notify() 方法仅通知一个线程。
notifyAll() 方法可以使所有正在等待队列中等待同一资源的全部线程从等待状态退出,进入可运行状态。此时,优先级最高的那个线程最先执行,但也有可能随机执行,这取决于 JVM 虚拟机的实现。

在这里插入图片描述

  1. 新创建一个线程后再调用它的 start() 方法,系统会为此线程分配 CPU 资源,使其处于 Runnable(可运行)状态,这是一个准备运行的阶段。如果线程抢到 CPU 资源,该线程就处于 Running(运行)状态。
  2. Running 状态和 Runnable 状态可相互切换,因为有可能线程运行一段时间以后,有其他优先级高的线程抢占了 CPU 资源,此时线程就从 Running 变成了 Runnable。
    线程进入 Runnable 状态大体分为 5 中情况:
  • 调用 sleep() 方法后经过的时间超过了指定的休眠时间
  • 线程调用的阻塞 IO 已经返回,阻塞方法执行完毕
  • 线程成功地获得了试图同步的监视器
  • 线程正在等待某个通知,其他线程发出了通知
  • 处于挂起状态的线程调用了 resume 恢复方法
  1. Blocked 是阻塞的意思,例如遇到一个 IO 操作,此时 CPU 处于空闲状态,可能转而会把 CPU 时间分配给其他线程,这时也称之为暂停状态。
    出现阻塞大体分为 5 中情况:
  • 线程调用 sleep 方法,主动放弃占用的处理器资源
  • 线程调用了阻塞的 IO 方法,在方法返回前,该线程被阻塞
  • 线程试图获得一个同步监视器,但该同步监视器正在被其他线程所持有
  • 线程等待某个通知
  • 程序调用了 suspend 方法将该线程挂起
  1. run() 方法运行结束后进入销毁阶段,整个线程执行完毕。

每个所对象都有两个队列,一个是就绪队列,一个是阻塞队列。就绪队列存储了将要得锁的线程,阻塞队列存储了被阻塞的线程。

方法 wait() 锁释放与 notify() 锁不释放

当方法 wait() 被执行后,锁自动释放,但执行完 notify() 方法,锁却不自动释放。

wait() 方法被执行以后,锁自动释放:

public class Service {
    public void testMethod(Object lock){
        try {
            synchronized (lock){
                System.out.println(Thread.currentThread().getName() + "begin wait");
                lock.wait();
                System.out.println("end wait");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class ThreadA extends Thread {
    private Object lock;

    public ThreadA(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.testMethod(lock);
    }
}

public class ThreadB extends Thread {
    private Object lock;

    public ThreadB(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.testMethod(lock);
    }
}

public class Test {
    public static void main(String[] args) {
        Object lock = new Object();
        ThreadA a = new ThreadA(lock);
        a.start();
        ThreadB b = new ThreadB(lock);
        b.start();
    }
}

运行结果:
Thread-0begin wait
Thread-1begin wait

方法 notify() 执行后,不释放锁:

public class Service {
    public void testMethod(Object lock){
        try {
            synchronized (lock){
                System.out.println("begin wait ThreadName = " + Thread.currentThread().getName());
                lock.wait();
                System.out.println("end wait ThreadName = " + Thread.currentThread().getName());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void synNotifyMethod(Object lock){
        try {
            synchronized (lock){
                System.out.println("begin notify ThreadName = " + Thread.currentThread().getName() +
                        "time = " + System.currentTimeMillis());
                //lock.notify()这句代码执行完毕以后还是不会释放锁,要等到notify所在的代码块中的代码全部执行完毕以后才会释放锁
                lock.notify();
                Thread.sleep(5000);
                System.out.println("end notify ThreadName = " + Thread.currentThread().getName() +
                        "time = " + System.currentTimeMillis());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class ThreadA extends Thread {
    private Object lock;

    public ThreadA(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.testMethod(lock);
    }
}

public class NotifyThread extends Thread{
    private Object lock;

    public NotifyThread(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.synNotifyMethod(lock);
    }
}

public class synNotifyMethodThread extends Thread {
    private Object lock;

    public synNotifyMethodThread(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.synNotifyMethod(lock);
    }
}

public class Test {
    public static void main(String[] args) throws InterruptedException{
        Object lock = new Object();
        ThreadA threadA = new ThreadA(lock);
        threadA.start();
        NotifyThread notifyThread = new NotifyThread(lock);
        notifyThread.start();
        synNotifyMethodThread c = new synNotifyMethodThread(lock);
        c.start();
    }
}

只通知一个线程

调用 notify() 一次只随机通知一个线程进行唤醒。

多生产者与多消费者:操作值-假死

一生产与一消费:操作栈

本示例是使生产者向堆栈 List 对象中放入数据,使消费者从 List 堆栈中取出数据。List 的最大容量是 1。

通过管道进行线程通信

在 Java 语言中提供了各种各样的输入 / 输出流 Stream,其中管道流是一种特殊的流,用在不同线程间直接发送数据。一个线程发送数据到输出管道,另一个线程从输入管道中读数据。通过管道,实现不同线程间的通信,而无须借助于类似临时文件之类的东西。
Java JDK 提供了四个类来使线程间可以进行通信:

  1. PipedInputStream 和 PipedOutputStream
  2. PipedReader 和 PipedWriter

方法 join 的使用

在很多情况下,主线程成创建并启动子线程,如果子线程要进行大量耗时的运算,主线程往往早于子线程结束之前结束。这是,如果主线程想要等待子线程执行完之后再结束,比如子线程处理一个数据,主线程要得到这个数据中的值,就要用到 join() 方法了。方法 join 的作用是等待线程对象销毁。

学习方法 join 前的铺垫

使用 join() 方法来解决

public class MyThread extends Thread {
    @Override
    public void run() {
        try {
            int secondValue = (int) (Math.random() * 10000);
            System.out.println(secondValue);
            Thread.sleep(secondValue);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class Test {
    public static void main(String[] args) {
        try {
            MyThread threadTest = new MyThread();
            threadTest.start();
            threadTest.join();
            System.out.println("我想当threadTest对象执行完毕后再执行,我做到了");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

方法 join() 的作用是使所属的线程对象 x 正常执行 run() 方法中的任务,而使当前线程 z 进行无限期的阻塞,等待线程 x 销毁后再继续运行线程 z 后边的代码。join 与 synchronized 的区别是:join() 内部使用 wait() 方法进行等待,而 synchronized 关键字使用“对象监视器”原理作为同步。

join() 方法与异常

在 join 过程中,如果当前线程对象被中断,则当前线程出现异常。

方法 join(long) 的使用

方法 join(long) 中的参数是设定等待的时间。

方法 join(long) 和 sleep(long) 的区别

方法 join(long) 的功能在内部是使用 wait(long) 方法来实现的,所以 join(long) 方法具有释放锁的特点。

下面实验验证了 sleep(long) 不释放锁

public class ThreadA extends Thread {
    private ThreadB b;

    public ThreadA(ThreadB b) {
        super();
        this.b = b;
    }

    @Override
    public void run() {
        try {
            synchronized (b){
                b.start();
                Thread.sleep(8000);//不释放锁,这个sleep是对a线程的休眠(当前线程),在a线程休眠的时候还是持有该锁的,所以此时别的需要获得该锁的线程不能执行
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class ThreadB extends Thread {
    @Override
    public void run() {
        try {
            System.out.println("b run begin time = " + System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("b run end time = " + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    synchronized public void bService(){
        System.out.println("打印了bService time = " + System.currentTimeMillis());
    }
}

public class ThreadC extends Thread {
    private ThreadB threadB;

    public ThreadC(ThreadB threadB) {
        super();
        this.threadB = threadB;
    }

    @Override
    public void run() {
        threadB.bService();
    }
}

public class Run {
    public static void main(String[] args) {
        try {
            ThreadB b = new ThreadB();
            ThreadA a = new ThreadA(b);
            a.start();
            Thread.sleep(1000);//对主线程的休眠
            ThreadC c = new ThreadC(b);
            c.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

下面实验验证了 join(long) 释放锁

public class ThreadA extends Thread {
    private ThreadB b;

    public ThreadA(ThreadB b) {
        super();
        this.b = b;
    }

    @Override
    public void run() {
        try {
            //运行到这里的时候a线程持锁
            synchronized (b){
                b.start();
                //b.join()的意思是a线程等到b线程运行完以后再运行后边的代码,同时a线程释放锁
                b.join();
                System.out.println("thread a end");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

类 ThreadLocal 的使用

猜你喜欢

转载自blog.csdn.net/qq_32682177/article/details/84528947