线程开始运行,拥有自己的栈空间,那多个线程如何相互配合完成工作,这就涉及到了线程间的通信。
线程通信是使线程间能够互相发送信号,是使线程能够等待其他线程的信号。比如线程 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();
}
}
}
}
}
运行结果