线程间的通信

414 阅读4分钟

使用wait/notify 实现线程间的通信

API

obj.wait() 让进入 object 监视器的线程到 waitSet 等待

obj.notify() 在 object 上正在 waitSet 等待的线程中挑一个唤醒

obj.notifyAll() 让 object 上正在 waitSet 等待的线程全部唤醒

都属于Object的方法,必须获得此对象的锁才能调用

import lombok.extern.slf4j.Slf4j;

/**
 * @Author blackcat
 * @create 2021/7/25 21:00
 * @version: 1.0
 * @description:wait、notify、notifyAll api
 */
@Slf4j
public class ThreadWait {

    final static Object lock = new Object();

    public static void main(String[] args) {

        new Thread(() -> {
            log.info("wait1 enter");
            synchronized (lock) {
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.info("wait1 out");
        }, "wait1").start();


        new Thread(() -> {
            log.info("wait2 enter");
            synchronized (lock) {
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.info("wait2 out");
        }, "wait2").start();

        try {
            Thread.sleep(2 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


        synchronized (lock) {
            //   lock.notify();
            lock.notifyAll();
        }
    }

}

//notifyAll()的结果
//21:02:39.005 [wait2] INFO  - wait2 enter
//21:02:39.005 [wait1] INFO  - wait1 enter
//21:02:41.016 [wait1] INFO  - wait1 out
//21:02:41.016 [wait2] INFO  - wait2 out

//notify()的结果
//21:02:39.005 [wait2] INFO  - wait2 enter
//21:02:39.005 [wait1] INFO  - wait1 enter
//21:02:41.016 [wait2] INFO  - wait2 out

原理

Monitor:对象的监视器,只要发生同步操作,线程就为当前对象创建一个Monitor对象与之关联,Monitor只能被一个线程持有,此时当前对象就处于锁定状态,其它线程只能阻塞等待。

Java虚拟机(HotSpot)中,Monitor是通过ObjectMonitor实现的(c++),里面有三个重要的属性

ObjectMonitor() {
_WaitSet  = NULL; //处于wait状态的线程,会被加入到_WaitSet
_EntryList = NULL ; //处于等待锁block状态的线程,会被加入到该列表
_owner = NULL;
_count = 0; //记录个数
}

Monitor 有两个队列 WaitSet 和 _EntryList,存储ObjectWaiter列表(所有等待的线程都会被包装成ObjectWaiter);① 线程申请owner Monitor对象,首先会被加入到 _EntryList ;② 线程申请owner Monitor对象,进入到 Owner区域,此时count +1; ③线程调用wait方法,进入到 WaitSet ,释放锁,此时count -1 ④ 线程再次申请owner ⑤ 线程处理完毕后释放资源并退出。

Minor.png

保护性暂停模式

即 Guarded Suspension,一个线程等待另一个线程的执行结果

synchronized(lock) {
     while(条件不成立) {
        lock.wait();
     }
 // 干活
}
//另一个线程
synchronized(lock) {
    lock.notifyAll();
}
import lombok.extern.slf4j.Slf4j;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

/**
 * @Author blackcat
 * @create 2021/7/25 21:49
 * @version: 1.0
 * @description:保护性暂停模式
 */
@Slf4j
public class GuardedObjectTest {


    public static void main(String[] args) {
        //线程1等待线程2的结果
        GuardedObject object = new GuardedObject(1);

        new Thread(() -> {
            log.info("begin");
            Object response = object.get(2000L);
            log.info("end");
        }).start();

        new Thread(() -> {
            log.info("等待结果");
            Object response = object.get();
            log.info("get response: [{}] size", ((List<String>) response).size());
        }).start();

        new Thread(() -> {
            log.info("执行下载");
            try{
                List<String> download = Downloader.download();
                object.set(download);
            }catch (Exception e){
                e.printStackTrace();
            }

        }).start();
    }

}

class GuardedObject {

    private int id;

    private Object response;

    public GuardedObject(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    Object get() {
        synchronized (this) {
            while (response == null) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return response;
        }
    }


    Object get(Long timeout) {
        synchronized (this) {
            long begin = System.currentTimeMillis();
            long now = 0;
            while (response == null) {
                timeout = timeout - now;
                if (timeout <= 0) {
                    break;
                }
                try {
                    this.wait(timeout);
                    now = System.currentTimeMillis() - begin;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return response;
        }
    }

    void set(Object response) {
        synchronized (this) {
            this.response = response;
            this.notifyAll();
        }
    }
}

//下载工具类
class Downloader {
    public static List<String> download() throws IOException {
        HttpURLConnection conn = (HttpURLConnection) new URL("https://www.baidu.com/").openConnection();
        List<String> lines = new ArrayList<>();
        try (BufferedReader reader =
                     new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
            String line;
            while ((line = reader.readLine()) != null) {
                lines.add(line);
            }
        }
        return lines;
    }
}


生产者-消费者模式

import lombok.extern.slf4j.Slf4j;

import java.util.LinkedList;

/**
 * @Author blackcat
 * @create 2021/7/25 21:57
 * @version: 1.0
 * @description:生产者-消费者模式
 */
@Slf4j
public class MessageProduct {


    public static void main(String[] args) {
        MessageQueue queue = new MessageQueue(2);
        for (int i = 0; i < 4; i++) {
            int id = i;
            new Thread(() -> {
                queue.put(new Message(id, "生产者" + id));
            },"生产者"+id).start();
        }

        // 1 个消费者线程, 处理结果
        new Thread(() -> {
            while (true) {
                Message message = queue.get();
                Object response = (Object) message.getMsg();
                log.debug("take message({}): [{}] ", message.getId(), response);
            }
        }, "消费者").start();
    }
}


class Message {
    private int id;
    private Object msg;

    public Message(int id, Object msg) {
        this.id = id;
        this.msg = msg;
    }

    public int getId() {
        return id;
    }

    public Object getMsg() {
        return msg;
    }

    @Override
    public String toString() {
        return "Message{" +
                "id=" + id +
                ", msg=" + msg +
                '}';
    }
}

@Slf4j
class MessageQueue {

    private int capacity;

    private LinkedList<Message> list = new LinkedList<>();

    public MessageQueue(int capacity) {
        this.capacity = capacity;
    }

    public void put(Message msg) {
        synchronized (this) {
            while (list.size() >= capacity) {
                try {
                    log.debug("put wait");
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.debug("生产{}",msg);
            list.add(msg);
            this.notifyAll();
        }
    }


    public Message get() {
        synchronized (this) {
            while (list.isEmpty()) {
                try {
                    log.debug("get wait");
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            Message first = list.removeFirst();
            this.notifyAll();
            return first;
        }

    }
}

join

import lombok.extern.slf4j.Slf4j;

/**
 * @Author blackcat
 * @version: 1.0
 * @description:
 */
@Slf4j
public class MakeTea {


    public static void main(String[] args) {
        makeTea();
    }

    private static void makeTea() {
        //洗水壶 -->烧开水
        Thread t1 = new Thread(()->{
            log.info("洗水壶");
            sleep(1);
            log.info("烧开水");
            sleep(5);
        },"老王");

        //洗茶壶 --> 洗茶杯 -->拿茶叶
        //当前线程等待水烧开再泡茶
        Thread t2 = new Thread(()->{
            log.info("洗茶壶");
            sleep(1);
            log.info("洗茶杯");
            sleep(2);
            log.info("拿茶叶");
            sleep(1);
            try {
                t1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("泡茶");
        },"小王");
        t1.start();
        t2.start();
    }

    private static void sleep(int i ) {
        try {
            Thread.sleep(i * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Threadlocal

Java中的ThreadLocal类可以让你创建的变量只被同一个线程进行读和写操作。

import lombok.extern.slf4j.Slf4j;

/**
 * @Author blackcat
 * @create 2021/7/25 22:16
 * @version: 1.0
 * @description:ThreadLocalExample
 */
@Slf4j
public class ThreadLocalExample {

    public static class MyRunnable implements Runnable {
        private ThreadLocal<Integer> threadLocal =  new ThreadLocal<Integer>();


        @Override
        public void run() {
            threadLocal.set( (int) (Math.random() * 100) );
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("get result:{}",threadLocal.get());
        }
    }

    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        Thread  thread1 = new Thread(runnable,"t1");
        Thread  thread2 = new Thread(runnable,"t2");
        thread1.start();
        thread2.start();
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

同一个ThreadLocal变量在父线程中被设置值后,在子线程中是获取不到的。

import lombok.extern.slf4j.Slf4j;

/**
 * @Author blackcat
 * @create 2021/7/25 22:27
 * @version: 1.0
 * @description:ThreadLocal,InheritableThreadLocal
 */
@Slf4j
public class ThreadLocalTest {


    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    private static InheritableThreadLocal<String> inThreadLocal = new InheritableThreadLocal<>();

    public static void main(String[] args) {
        //在main线程中添加main线程的本地变量
        threadLocal.set("mainVal");
        inThreadLocal.set("inMainVal");
        //新创建一个子线程
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                log.info("子线程中的本地变量值:{}" ,threadLocal.get());
                log.info("子线程中的in本地变量值:{}" ,inThreadLocal.get());
            }
        });
        thread.start();
        //输出main线程中的本地变量值
        log.info("main线程中的本地变量值:{}" ,threadLocal.get());
        log.info("main线程中的in本地变量值:{}" ,inThreadLocal.get());
    }
}