线程间通信 wait()、notify()、notifyAll(

2020-01-18  本文已影响0人  z白依

本篇主要讲解线程间通讯,如何启动一个线程,如何结束一个线程,wait()、notify()、notifyAll() 到底做了什么事, 阅读本篇先要理解 synchronized 的原理,可以先看下Java多线程、synchronize原理。话不多说,直接进入正题。

线程间通讯


线程间通讯,很简单,就是一个线程指挥另一个线程做事,或者说给另一个线程发消息。其实并不是我们想象的那种线程与线程对话沟通这种方式,Java也没有这种操作可以用。所谓的线程间交互其实用的是一个线程启动别的线程和一个线程终结别的线程。

启动线程

启动一个线程就是开一个新线程了,相信大家都知道,简单点就是thread.start()

结束线程

使用 thread.stop() 可以结束线程,简单直接,而且很稳定,每次调用必然把线程杀死。但是这个方法已经被弃用了。

stop() 为何弃用

原因就是 这个方法太直接太猛了从而导致结果不可预期。

线程在执行的过程中任何适合别人都可能调用stop() 来停止它,而且从外面一调用直接就被停了,那么任何两行代码之间或者一行代码执行过程之中都有可能被掐断,这样的话,这线程可能做事做到一半,那么程序的状态可能就是一个中间状态。

stop() 这个方法会导致线程停止的时候程序的状态是不可控的是不可预期的,这个东西还无解。所以就是stop很好用很直接就是太生猛了。并不是stop() 怎么差了,它是很稳定的,每次调用线程都会终结。而是因为使用外力来终止线程的这种机制不可靠。

那怎么终结线程呢?

interrupt() 打断

thread.interrupt() 打断,只是把线程标记为中断状态,把线程的中断标志置为true。它连打断都不算,它只是一个标记。这是一个温和版本的终止。

特点:不是立即的,不是强制的。线程要自己支持使用 interrupt,才能被 interrupt 终止,才会有效。

public void test() {
    Thread thread = new Thread() {
        @Override
        public void run() {
            for (int i = 0; i < 1_000_000; i++) {
                if (isInterrupted()) {
                    return;
                }
                System.out.println(i);
            }
        }
    };
    thread.start();
    ...
//    thread.stop();
    thread.interrupt();
}
isInterrupted()

Thread 的方法,一调用这个方法就会判断中断标志是否被标记也就是是否调用过 interrupt()。一般这种检查会放在耗时操作之前。终结线程其实就是为了省时间。

Thread.interrupted()
if (Thread.interrupted()) {
   return;
}

是否被打断了。 和isInterrupted()就一点区别,会重置中断状态,把中断状态重新变为false。

重置有什么用? 比如:调用interrupt() 结束线程的时候,线程可以根据状态判断结束不结束。如果不满足条件你的打断就对我无效。

for (int i = 0; i < 1_000_000; i++) {
    if (Thread.interrupted()) {
        if (i > 500_000) {
            // 提前结束可以做一些收尾工作
            ...
            return;
        }
    }
    System.out.println(i);
}

InterruptedException


try {
    Thread.sleep(500);
} catch (InterruptedException e) {
    e.printStackTrace();
}

在 Java 中如果调用 Thread.sleep() 会强制抛异常 InterruptedException(重置中断状态)。被打断的exception。

public void test() {
    Thread thread = new Thread() {
        @Override
        public void run() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };
    thread.start();
    try {
        Thread.sleep(500);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    thread.interrupt();
}

它是一个正处于等待状态的线程的终止。支持被外部打断。支持被外部打断的时候才需要这个异常。

在 Android 中有一个SystemClock.sleep(),它为什么不需要try catch 呢?来看个源码,在内部调用Thread.sleep() 也是要强制抛异常的。而且可以看到在catch中 e.printStackTrace(); 被去掉了,就算被别人 interrupt() 了SystemClock也不会停止,不会听话,不支持外部打断。

public final class SystemClock {
    ...
    public static void sleep(long ms)
    {
        long start = uptimeMillis();
        long duration = ms;
        boolean interrupted = false;
        do {
            try {
                Thread.sleep(duration);
            }
            catch (InterruptedException e) {
                interrupted = true;
            }
            duration = start + ms - uptimeMillis();
        } while (duration > 0);

        if (interrupted) {
            Thread.currentThread().interrupt();
        }
    }
    ...
}

wait()、notify()、notifyAll()


先来看个demo,需求很简单开两个线程,一个线程给数据赋值,一个线程取数据。怎样保证取到的数据不为空呢?

public class Main {
    private String sharedString;

    private void initString() {
        sharedString = "testString";
    }

    private void printString() {
        System.out.println("String: " + sharedString);
    }

    public static void main(String[] args) {
        final Thread thread1 = new Thread() {
            @Override
            public void run() {
                ... //一些耗时操作
                printString();
            }
        };
        thread1.start();
        Thread thread2 = new Thread() {
            @Override
            public void run() {
                ... //一些耗时操作
                initString();
            }
        };
        thread2.start();
    }
}

要保证数据的同步性、互斥性,当然是用 synchronized 了。那么在什么时候thread1取到的数据不为空呢?修改下这两个方法。在printString()中加个循环。

private synchronized void initString() {
    sharedString = "rengwuxian";
}

private synchronized void printString() {
    while (sharedString == null) {
    }
    System.out.println("String: " + sharedString);
}

可以看到如果是这样的话,可能 thread1就会拿着锁一直不释放了。而thread2就会一直在monitor排队。现在就该 wait() 上场了。

private synchronized void initString() {
    sharedString = "testString";
    notify();
}

private synchronized void printString() {
    while (sharedString == null) {
        try {
            wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    System.out.println("String: " + sharedString);
}

先来讲一下wait() 的工作机制:调用 wait(), thread1就会到一个等待区域(等待集合)等待。这样就把monitor给释放了。notify() 就是把等待区域中的某一个唤醒,这个线程就会被唤醒到monitor排队。notifyAll()是把所有都唤醒。

monitor 是啥,synchronized 原理。查看上一篇Java多线程、synchronize原理

需要注意的几点:wait()也是一个处于等待状态的方法,需要强制抛异常,也可以被打断。wait()、notify()、notifyAll()必须在synchronized里面被调用。这三个方法是属于 monitor的方法。看下完整代码,这次使用另一个monitor。

public class Main {
    private String sharedString;
    private final Object monitor = new Object();

    private void initString() {
        synchronized (monitor) {
            sharedString = "testString";
            monitor.notifyAll();
        }
    }

    private void printString() {
        synchronized (monitor) {
            while (sharedString == null) {
                try {
                    monitor.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("String: " + sharedString);
        }
    }

    public static void main(String[] args) {
        final Thread thread1 = new Thread() {
            @Override
            public void run() {
                ... //一些耗时操作
                printString();
            }
        };
        thread1.start();
        Thread thread2 = new Thread() {
            @Override
            public void run() {
                ... //一些耗时操作
                initString();
            }
        };
        thread2.start();
    }
}

总结


本篇介绍了wait()、notify()、notifyAll() 原理以及使用,最主要的还是对 synchronized 原理的理解。这三个方法其实是monitor的方法,这也就是为什么这三个方法是在 Object 中的。因为只要是不变的对象都可以用作monitor啊。

上一篇下一篇

猜你喜欢

热点阅读