Java 线程通信

当我需要多个线程相互协作时,我要掌握 Java 线程的通信方式。

对象锁 synchronized

Java 中,锁的概念都是基于对象的,所以我们称它为对象锁。一个锁同一时间只能被一个线程持有。

同步块,是只能有一个线程运行的代码块。

为了到达同步的效果,我们使用 synchronized 来实现。

例如 打印 0-100 必须是连续的 如下

public static void main(String[] args) throws InterruptedException {
    Object lock = new Object();
    Runnable runnable = () -> {
        synchronized (lock) {
            for (int i = 1; i <= 100; i++) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    };
    Thread t1 = new Thread(runnable);
    t1.setName("A");

    Thread t2 = new Thread(runnable);
    t2.setName("B");

    t1.start();
    t2.start();
}

我们声明了一个 lock 的对象,在需要原子执行的代码块里,使用 synchronized 关键字 加上同一个对象锁 lock.

对象 等待/通知 wait/notify

上面基于锁的方式,线程需要不断尝试获取锁,如果失败了,再继续尝试。这可能会耗费服务器资源。

Java 的等待/通知 机制是基于 Object类的 wait,notify notifyAll 方法实现

notify 会随机叫醒一个正在等待的线程,而notifyAll()会叫醒所有正在等待的线程。

线程只有获取到对象锁后,才能使用 lock.wait()让自己进入等待状态。这时,lock锁被释放掉。

这时,线程 B 获得了 lock 并继续执行,线程 B 使用lock.notify(),通知之前持有 lock 并进入等待状态的线程 A, 不用等了,可以过来抢锁了。

注意,此时 线程 B 并未释放 lock, 除非 B 使用 lock.wait 释放锁,或者 B 执行完执行释放锁, 线程A 才能得到 lock

下面的代码使用 通知等待机制实现交替打印 A/B

   public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        Runnable runnable = () -> {
            synchronized (lock) {
                while (true) {
                    System.out.println(Thread.currentThread().getName());
                    lock.notifyAll();
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        };
        Thread t1 = new Thread(runnable);
        t1.setName("A");

        Thread t2 = new Thread(runnable);
        t2.setName("B");

        t1.start();
        t2.start();
    }

信号量 volatile

volatile 关键字保证内存可见性,如果用 volatile 声明了一个变量,在一个线程里改变了它的值,其他线程可以立马看见最新的值。

管道

基于管道流的方式,DK提供了PipedWriterPipedReaderPipedOutputStreamPipedInputStream.

public static void main(String[] args) throws Exception {
    final PipedWriter writer = new PipedWriter();
    final PipedReader reader = new PipedReader();
    writer.connect(reader);
    Thread readerThread = new Thread(() -> {
        System.out.println("this is reader");
        int receive = 0;
        try {
            while ((receive = reader.read()) != -1) {
                System.out.print((char) receive);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    });

    Thread writerThread = new Thread(() -> {
        System.out.println("this is writer");
        try {
            writer.write("test");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                writer.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    });

    readerThread.start();
    Thread.sleep(1000);
    writerThread.start();
}

当一个线程需要向另一个线程发送消息或文件,就要用到管道了。

其他

join

让当前线程进入等待状态。等调用join的线程实例执行完。当前线程处于 WAITING

sleep

静态方法,让当前线程睡眠一段时间,当前线程 进入 TIMED_WAITING

sleep 方法不会释放当前锁,wait方法会。

wait 与 sleep 的区别

  • sleep 必须指定时间,时间对于wait是可选项。

  • wait 释放cpu资源,同时释放锁,sleep释放cpu资源,但是不释放锁

  • wait 必须再同步块或同步方法,sleep可以再任意位置

ThreadLocal

ThreadLocal是一个本地线程副本变量工具类。内部是一个弱引用的 Map。

每个线程都有自己独立的变量,线程间互不影响。

如果只是为了隔离线程,声明一个局部变量就可以了,为什么要使用 ThreadLocal?

变量与线程关联,可以随时取到值,不用传参。

ThreadLocal 的使用场景为

  • 数据库连接
  • Session管理

InheritableThreadLocal

和 ThreadLocal 稍有不同,扩展为 子线程也可以存取这个变量值。

Last Updated:
Contributors: himcs