线程间通信

2018-11-18  本文已影响10人  沐小晨曦

概述

线程间的通信有以下几种方式:

  1. volatile 和 synchronized 关键字
  2. 等待 / 通知机制
  3. 管道输入/ 输出流
  4. Thread.join() 的使用
  5. ThreadLocal

volatile 与 synchronized

volatile 与 synchronized 保证可见性,可见性如果用内存模型来阐述,那就是在变量的写的时候,把把该变量同步到主内存,其他线程通过嗅探主内存的该变量值是否变化,在变化的时候更新自己的工作内存,这样就完成了线程之间的交互。

等待 / 通知机制

等待 / 通知的相关方法是任意 Java 对象都具备的,因为这些方法被定义在所有对象的超类 java.lang.Object 上:

方法名称 描述
notify() 通知一个在对象上等待的线程,使其从 wait() 方法返回,而返回的前提是该线程获取到了对象的锁
notifyAll() 通知所有等待在该对象上的线程
wait() 调用该方法的线程进入 WAITING 状态,只有等待另外线程的通知或被中断才会返回,需要注意,调用 wait() 方法后,会释放对象的锁
wait(long) 超时等待一段时间,这里的参数时间单位是毫秒,如果没有通知就超时返回

等待 / 通知机制,是指一个线程 A 调用了对象 O 的 wait 方法进入等待状态,而另外一个线程 B 调用了对象 O 的 notify 或者 notifyAll 方法,线程 A 收到通知后从对象 O 的 wait 方法返回,进而执行后续操作。上诉两个线程通过对象 O 完成交互,而对象上的 wait 方法和 notify / notifyAll 的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。

在使用以上方法需要注意几点:

  1. 使用 wait、notify、notifyAll 时需要先对调用对象加锁
  2. 调用 wait 方法后,线程状态由 RUNNING 变为 WAITING,并将当前线程放置到对象的等待队列
  3. notify 或 notifyAll 方法调用之后,等待线程依旧不会从 wait 返回,需要调用 notify 或 notifyAll 的线程释放锁之后,等待线程才有机会从 wait 方法
  4. notify 方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而 notifyAll 方法则是将等待队列中所有的线程全部移到同步队列,被移动的线程状态由 WAITING 变为 BLOCKED
  5. 从 wait 方法返回的前提是获得了调用对象的锁

管道输入 / 输出流

管道输入 / 输出流和普通的文件输入 / 输出流或者网络输入 / 输出流不同之处在于,它主要用于线程之间的数据传输,而传输的媒介为内存。

管道输入 / 输出流主要包括了如下四种具体实现:PipedOutputStream、PipedInputStream、PipedReader、PipedWriter,前两种面向字节,而后两种面向字符。

public class PipedDemo {
    public static void main(String[] args) throws IOException {
        PipedReader pipedReader = new PipedReader();
        PipedWriter pipedWriter = new PipedWriter();
        //将输入输出流进行链接,否则会抛出 IOException
        pipedWriter.connect(pipedReader);
        Thread printThread = new Thread(new PrintRunnable(pipedReader));
        printThread.start();
        int receive = 0;
        try {
            while ((receive = System.in.read()) != -1) {
                pipedWriter.write(receive);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            pipedWriter.close();
        }
    }

    static class PrintRunnable implements Runnable {
        private PipedReader pipedReader;

        public PrintRunnable(PipedReader pipedReader) {
            this.pipedReader = pipedReader;
        }

        @Override
        public void run() {
            int receive = 0;
            try {
                while ((receive = pipedReader.read()) != -1) {
                    System.out.println("输出:" + (char) receive);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
/**
 * Demo
 * 输出:D
 * 输出:e
 * 输出:m
 * 输出:o
 * 输出
 */

Thread.join() 的使用

如果一个线程 A 执行了 thread.join() 语句,其含义是:当前线程 A 等待 thread 线程终止之后才从 thread.join() 返回。线程 Thread 除了提供 join 方法之外,还提供了 join(long millis)等两个具备超时特性的方法。这两个超时方法表示,如果线程 thread 在给定的超时时间里没有终止,那么将会从该超时方法中返回。

public class JoinDemo {
    public static void main(String[] args) {
        Thread previous = Thread.currentThread();
        for (int i = 0; i < 10; i++) {
            //每个线程拥有前一个线程的引用,需要等待前一个线程终止,才能从等待中返回
            Thread thread=new Thread(new MyRunnable(previous),String.valueOf(i));
            thread.start();
            previous=thread;
        }
    }

    static class MyRunnable implements Runnable{
        private Thread thread;

        public MyRunnable(Thread thread) {
            this.thread = thread;
        }

        @Override
        public void run() {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+" terminate ");
        }
    }
}
    /**
     * 0 terminate 
     * 1 terminate 
     * 2 terminate 
     * 3 terminate 
     * 4 terminate 
     * 5 terminate 
     * 6 terminate 
     * 7 terminate 
     * 8 terminate 
     * 9 terminate
     */

从输出可以看出,每个线程终止的前提是前驱线程的终止,每个线程等待前驱线程终止后,才从 join 方法返回,这里涉及了等待 / 通知记住(等待前驱线程结束,接受前驱线程结束的通知)。

当线程终止时,会调用线程自身的 notifyAll 方法,会通知所有等待在该线程对象上的线程。

ThreadLocal 的使用

ThreadLocal,即线程变量,是一个以 ThreadLocal 对象为键、任意对象为值的存储结构。这个结构被依附在线程上,也就是说一个线程可以根据一个 ThreadLocal 对象查询到绑定在这个线程上的一个值。

可以通过 set(T) 方法来设置一个值,在当前线程下再通过 get() 方法获取到原先设置的值。

public class ThreadLocalDemo {

    private static final ThreadLocal<Long> TIME_THREADLOCAL = new ThreadLocal<>() {
        @Override
        protected Long initialValue() {
            return System.currentTimeMillis();
        }
    };

    public static final void begin() {
        TIME_THREADLOCAL.set(System.currentTimeMillis());
    }

    public static final long end() {
        return System.currentTimeMillis() - TIME_THREADLOCAL.get();
    }

    public static void main(String[] args) throws InterruptedException {
        ThreadLocalDemo.begin();
        Thread.sleep(2000);
        System.out.println(ThreadLocalDemo.end());
    }
}
//输出 2003
上一篇下一篇

猜你喜欢

热点阅读