【多线程】Java线程间是如何通信的呢?


线程开始运行,拥有自己的栈空间,那多个线程如何相互配合完成工作,这就涉及到了线程间的通信。
线程通信是使线程间能够互相发送信号,是使线程能够等待其他线程的信号。比如线程 A 在执行到某个条件通知线程 B 执行某个操作

一、共享内存机制

(1)同步–synchronized

线程同步是线程之间按照⼀定的顺序执⾏,可以使⽤锁来实现达到线程同步,也就是在需要同步的代码块里加上关键字synchronized 。因为⼀个锁同⼀时间只能被⼀个线程持有。

关键字synchronized可以修饰方法或者以同步块,它主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性。

这种方式,线程需要不断地去尝试获得锁,如果失败了,再继续尝试。这可能会耗费服务器资源。

基于synchronized实现线程A和线程B通信
线程A执行完,再让线程B执行,使用对象锁实现

public class ObjectLock {
    private static Object lock = new Object();
    static class ThreadA implements Runnable {
        @Override
        public void run() {
            synchronized (lock) {
                for (int i = 0; i < 5; i++) {
                    System.out.println("ThreadA " + i);
                }
            }
        }
    }

    static class ThreadB implements Runnable {

        @Override
        public void run() {
            synchronized (lock) {
                for (int i = 0; i < 5; i++) {
                    System.out.println("ThreadB " + i);
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(new ThreadA()) .start();
        Thread.sleep(10);// 这里让线程睡10毫秒,可确保A先获得锁
        System.out.println("---------------------");
        new Thread(new ThreadB()).start();
    }
}

运行结果:
在这里插入图片描述

线程A和线程B需要访问同一个对象lock,谁获得锁,谁就先执行,这里控制的是让线程A先执行(Thread.sleep(10)为的就是A先获得锁)。线程B要等线程A执行完再执行,所以是同步的,这就实现了线程间的通信

(2)信号量 --volatile

Java支持多个线程同时访问一个对象或者对象的成员变量,由于每个线程可以拥有这个变量的拷贝,所以程序在执行过程中,一个线程看到的变量并不一定是最新的。
关键字volatile可以用来修饰字段(成员变量),就是告知程序任何对该变量的访问均需要从共享内存中获取,而对它的改变必须同步刷新回共享内存,它能保证所有线程对变量访问的可见性。

volitile关键字能够保证内存的可⻅性,如果⽤volitile关键字声明了⼀个变量,在⼀个线程⾥⾯改变了这个变量的值,那其它线程是⽴⻢可⻅更改后的值的。

基于volatile关键字实现线程A和线程B的通信
线程A输出0,然后线程B输出1,再然后线程A输出2…

public class Signal {
    private static volatile int signal = 0;
    static class ThreadA implements Runnable {
        @Override
        public void run() {
            while (signal < 5) {
                if (signal % 2 == 0) {
                    System.out.println("threadA: " + signal);
                    synchronized (this) {
                        signal++;
                    }
                }
            }
        }
    }
    static class ThreadB implements Runnable {
        @Override
        public void run() {
            while (signal < 5) {
                if (signal % 2 == 1) {
                    System.out.println("threadB: " + signal);
                    synchronized (this) {
                        signal = signal + 1;
                    }
                }
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        new Thread(new ThreadA()).start();
        Thread.sleep(10);
        new Thread(new ThreadB()).start();
    }
}

运行结果
在这里插入图片描述

volatile 变量需要进⾏原⼦操作。 signal++ 并不是⼀个原⼦操
作,所以我们需要使⽤ synchronized 给它“上锁”

关于synchronized与volatile ,synchronized主要做的是多线程顺序执行,也就是同一个时间只有一个线程在执行,线程A执行完了再让线程B执行,volatile主要做的是让多线程间共享的变量保证一致,也就是线程A对变量操作了,线程B对变量操作时是知道线程A对变量的操作的,是在线程A操作后的变量上进行操作。

二、等待/通知机制(wait/notify)

一个线程修改了一个对象的值,而另一个线程感知到了变化,然后进行相应的操作,整个过程开始于一个线程,而最终执行又是另一个线程
等待/通知机制使⽤的是使⽤同⼀个对象锁,如果你两个线程使
⽤的是不同的对象锁,那它们之间是不能⽤等待/通知机制通信的。

等待/通知的相关方法

方法名称 含义
notify() 通知一个对象上等待的线程,使其从wait()方法返回,而返回的前提是该线程获取到了对象的锁
notifyAll() 通知所有等待在该对象上的线程
wait() 调用该方法的线程进入WAITING状态,只有等待另外线程的通知或被中断才会返回,调用wait()方法后,会释放对象的锁
wait(long) 超时等待一段时间,没有通知就超时返回,参数时间是毫秒
wait(long,int) 对于超时时间更细粒度的控制,可达到纳秒

notify()和notifyAll()的区别:
notify()⽅法会随机叫醒⼀个正在等待的线程,⽽notifyAll()会叫醒所有正在等待的线程。

⼀个锁同⼀时刻只能被⼀个线程持有,lock.wait() 让⾃⼰进⼊等待状态,会释放锁
lock.notify() 叫醒⼀个正在等待的线程,但并没有释放锁 lock。

基于Object 类的 wait() ⽅法和 notify() ⽅法实现
线程A执行完,线程B执行,再线程A执行…

public class WaitNotify {
    private static Object lock = new Object();
    static class ThreadA implements Runnable {
        @Override
        public void run() {
            synchronized (lock) {
                for (int i = 0; i < 5; i++) {
                    try {
                        System.out.println("ThreadA: " + i);
                        lock.notify();
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                lock.notify();
            }
        }
    }
    static class ThreadB implements Runnable {
        @Override
        public void run() {
            synchronized (lock) {
                for (int i = 0; i < 5; i++) {
                    try {
                        System.out.println("ThreadB: " + i);
                        lock.notify();
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                lock.notify();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(new ThreadA()).start();
        Thread.sleep(10);
        new Thread(new ThreadB()).start();
    }
}

运行结果
在这里插入图片描述

三、管道

管道是基于“管道流”的通信⽅式,管道输入/输出流要用于线程之间的数据传输,而传输的媒介为内存。
管道输入/输出流的体现:
基于字符的:PipedWriter 、 PipedReader 、
基于字节流的:PipedOutputStream 、 PipedInputStream

使⽤管道多半与I/O流相关。当我们⼀个线程需要先另⼀个线程发送⼀个信息(⽐如字符串)或者⽂件等等时,就需要使⽤管道通信了

像消息传递机制,通过管道,将一个线程中的消息发送给另一个

基于PipedWriter 和PipedReader 的实现的ReaderThread 和WriterThread 的通信
WriterThread 写了内容,ReaderThread 读到并打印

public class Piped {
    public static void main(String[] args) throws IOException, InterruptedException {
        PipedWriter writer = new PipedWriter();
        PipedReader reader = new PipedReader();
        writer.connect(reader);
        new Thread(new ReaderThread(reader)).start();
        Thread.sleep(10);
        new Thread(new WriterThread(writer)).start();
    }
    static class ReaderThread implements Runnable{
        private PipedReader in;
        public ReaderThread(PipedReader in){
            this.in=in;
        }
        @Override
        public void run() {
            System.out.println("this is a reader");
            int receice=0;
            try {
                while ((receice=in.read())!=-1){
                    System.out.println("read "+(char) receice);
                }
            }catch (IOException ex){
                ex.printStackTrace();
            }

        }
    }

    static class WriterThread implements Runnable{
        private PipedWriter out;
        public WriterThread(PipedWriter out){
            this.out=out;
        }
        @Override
        public void run() {
            System.out.println("this is a writer");
            try {
                out.write("write A");
            }catch (IOException ex){
                ex.printStackTrace();
            }finally {
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

运行结果
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章