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提供了PipedWriter
、 PipedReader
、 PipedOutputStream
、 PipedInputStream
.
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 稍有不同,扩展为 子线程也可以存取这个变量值。