多线程(4)- 线程间通信


前言

同步阻塞与异步非阻塞、单线程通信,多线程通信
扩展:

概念

  1. JVM: Java虚拟机。

1、同步阻塞与异步非阻塞

  • 同步阻塞:每个请求Event过来同步调用方法,执行一系列业务操作,最后返回结果。
    • 客户等待时间长
    • 系统吞吐量低
    • 一个线程处理一个业务,线程频繁的创建开启与销毁,消耗资源多
    • 业务峰值,大量的业务处理线程会导致频繁的CPU上下文切换,从而降低性能
  • 异步非阻塞:每个请求过来,放入Event队列,主线程创建工单号,另外开启线程去处理工单号对应的业务,返回工单号。
    • 等待时间短
    • 提升系统吞吐量和并发量
    • 服务端线程数量控制在一定范围,不会导致太多的CPU切换从而带来开销
    • 服务端线程可以重复利用,减少了不断创建线程带来的资源浪费
    • 异步缺陷:需要再调用一次获取结果(异步回调接口-比如支付业务)

2、单线程间通信

  • 异步非阻塞如何知道Event队列有数据
    • 笨方法就是不断去轮询查,有数据就处理,没有数据就等待后再轮询
    • 通知机制,Event进队列就通知工作线程处理,没有就等待让工作线程休息
public class EventQueueMain {

    static class Event {

    }

    /**
     * 最大处理数 队列最大容量 10
     */
    public int               max = 10;
    private final       LinkedList<Event> eventQueue    = Lists.newLinkedList();

    public EventQueueMain(int max) {
        this.max = max;
    }

    /***
     * event请求进来
     */
    public void offer(Event event) {
        synchronized (eventQueue) {
            if (eventQueue.size() > max) {
                console(" queue already full");
                try {
                    eventQueue.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            console(" the new event is submitted");
            eventQueue.addLast(event);
            //当队列为空,请求一个Event,此时通知可以消费了
            this.eventQueue.notify();
        }
    }

    /**
     * 处理event请求
     */
    public void take() {
        synchronized (eventQueue) {
            if (CollectionUtils.isEmpty(eventQueue)) {
                try {
                    console(" deal  queue is empty");
                    eventQueue.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            Event event = eventQueue.removeFirst();
            //当队列已满,处理一个,此时通知可以继续生产
            this.eventQueue.notify();
            console("the event " + event + " is handled");
        }
    }

    public void console(String message) {
        System.out.printf(" %s  : %s \n", Thread.currentThread().getName(), message);
    }

    public static void main(String[] args) {
        EventQueueMain eventQueueMain = new EventQueueMain(10);
        new Thread(()->{
            for (;;){
                eventQueueMain.offer(new EventQueueMain.Event());
            }
        },"producer").start();

        new Thread(()->{
            for (;;){
                eventQueueMain.take();
                try {
                    TimeUnit.MILLISECONDS.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"consumer").start();

    }
}

 producer  :  the new event is submitted 
……
 producer  :  the new event is submitted 
 producer  :  queue already full 
 consumer  : the event com.today.roc.go.understand.thread.four.asyncNonBlocking.EventQueueMain$Event@3b66d775 is handled 
 producer  :  the new event is submitted 
 producer  :  queue already full 
 consumer  : the event com.today.roc.go.understand.thread.four.asyncNonBlocking.EventQueueMain$Event@7f43d966 is handled 
 producer  :  the new event is submitted 
 producer  :  queue already full

2.1 Object.wait

public final void wait() throws InterruptedException {wait(0);}
//0代表着永不超时
public final native void wait(long timeout) throws InterruptedException; 
//阻塞时间到了timeout自动唤醒
public final void wait(long timeout, int nanos) throws InterruptedException
wait方法必须拥有对象的monitor,必须在同步方法中使用
当前线程执行了该对象的wait方法后,会释放该对象的monitor,并进入到与该对象关联的wait set中,此时其它线程可以竞争对象的monitor所有权
notify之后,wait继续向下执行,应该内部逻辑从新拿到monitor所有权

2.2 Object.notify

唤醒单个执行该对象wait方法的线程
线程如果有执行该对象的wait方法,则会被唤醒,没有则会忽略
被唤醒线程必须重新获取对象所关联monitor的lock才能继续执行

2.3 wait、notify注意事项

  • wait是可中断方法,interrupt会打断抛出InterruptedException,同时interrupt标识会被擦除
  • 线程执行了某个同步对象的wait,会加入到对象的monitor关联的wait set中
  • 当线程进入wait set之后,notify会将其唤醒,从wait set中弹出
  • 同步方法中使用wait 和 notify方法,执行wait和notify的前提条件是必须持有同步方法monitor的所有权
  • 同步代码的monitor必须与执行wait notify方法的对象一致,哪个对象的同步就用哪个对象的wait notify

2.4 wait sleep区别

  • 都会产生阻塞,且均是可中断方法,中断后都可接收到InterruptedException
  • wait 是 Object方法, sleep是Thread方法
  • wait必须在同步方法中,sleep没有这个限制
  • wait会释放monitor所有权,sleep不会释放
  • sleep是一定时间后退出阻塞,wait可永久阻塞,可配合notify方法唤醒或中断退出阻塞

3、多线程间通信

  • 多线程通信使用notifyAll,处于wait再满足时,多个生产者消费者都参与竞争
  • 使用if只判断一次,就继续生产或消费,限制使用while循环,每次notifyAll之后,继续判断是否已经为空或超出边界。
public class MultiEventQueueMain {

    static class Event {

    }

    /**
     * 最大处理数 队列最大容量 10
     */
    public        int               max        = 10;
    private final LinkedList<Event> eventQueue = Lists.newLinkedList();

    public MultiEventQueueMain(int max) {
        this.max = max;
    }

    /***
     * event请求进来
     */
    public void offer(Event event) {
        console("come 0");
        synchronized (eventQueue) {
            console("sync 1");
            while (eventQueue.size() > max) {
                console(" queue already full");
                try {
                    eventQueue.wait();
                    console("wait after 2");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            console(" the new event is submitted");
            eventQueue.addLast(event);
            //当队列为空,请求一个Event,此时通知可以消费了
            this.eventQueue.notifyAll();
        }
    }

    /**
     * 处理event请求
     */
    public void take() {
        synchronized (eventQueue) {
            while (CollectionUtils.isEmpty(eventQueue)) {
                try {
                    console(" deal  queue is empty");
                    eventQueue.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            Event event = eventQueue.removeFirst();
            //当队列已满,处理一个,此时通知可以继续生产
            this.eventQueue.notifyAll();
            console("the event " + event + " is handled");
        }
    }

    public void console(String message) {
        System.out.printf(" %s  : %s \n", Thread.currentThread().getName(), message);
    }

    public static void main(String[] args) {
        MultiEventQueueMain eventQueueMain = new MultiEventQueueMain(10);
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                for (; ; ) {
                    eventQueueMain.offer(new MultiEventQueueMain.Event());
                }
            }, "producer"+i).start();
            new Thread(() -> {
                for (; ; ) {
                    eventQueueMain.take();
                    try {
                        TimeUnit.MILLISECONDS.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "consumer"+i).start();
        }
    }
}

4、自定义显示锁

多个线程竞争锁
public class BooleanLockTest {

    private final Lock lock = new BooleanLock();

    public void syncMethod() {
        //使用try finally 保证lock被回收
        try {
            lock.lock();
            int nextInt = current().nextInt(10);
            System.out.println(currentThread() + " get lock " + nextInt);
            TimeUnit.SECONDS.sleep(nextInt);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public List<Thread> getBlockThreads(){
        return lock.getBlockThreads();
    }

    public static void main(String[] args) throws InterruptedException{
        BooleanLockTest blt = new BooleanLockTest();
        //multiLock();
        interruptAble(blt);
        getBlockThreads(blt);
    }

    /**
     * interrupt able 可中断特性
     */
    private static void interruptAble(BooleanLockTest blt) throws InterruptedException {
        new Thread(()->{
            blt.syncMethod();
        },"T1").start();
        TimeUnit.MILLISECONDS.sleep(2);
        Thread t2 = new Thread(() -> {
            blt.syncMethod();
        }, "T2");
        t2.start();
        TimeUnit.MILLISECONDS.sleep(10);
        t2.interrupt();
    }

    /**
     * 多个线程争抢锁资源
     */
    private static void multiLock(BooleanLockTest blt) {
        IntStream.range(0, 10).mapToObj(v -> new Thread(blt::syncMethod)).forEach(Thread::start);
    }

    private static void getBlockThreads(BooleanLockTest blt) {
        Thread thread = new Thread(() -> {
            while (true) {
                List<Thread> threads = blt.getBlockThreads();
                String str = StringUtils.join(threads, ",");
                System.out.printf(" block thread : %s \n", str);
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.setDaemon(true);
        thread.start();
    }
}
锁中断能力
线程被中断时,需要从阻塞队列中移除,避免内存泄漏
/**
     * interrupt able 可中断特性
     */
    private static void interruptAble(BooleanLockTest blt) throws InterruptedException {
        new Thread(()->{
            blt.syncMethod();
        },"T1").start();
        TimeUnit.MILLISECONDS.sleep(2);
        Thread t2 = new Thread(() -> {
            blt.syncMethod();
        }, "T2");
        t2.start();
        TimeUnit.MILLISECONDS.sleep(10);
        t2.interrupt();
    }
    
 block thread :  
 T1  :2021-02-28 15:35:44.516try lock  success  
  T2  :2021-02-28 15:35:44.518try lock  wait  
 Thread[T1,5,main] get lock 7
java.lang.InterruptedException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at com.today.roc.go.understand.thread.four.booleanlock.BooleanLock.lock(BooleanLock.java:48)
	at com.today.roc.go.understand.thread.four.booleanlock.BooleanLockTest.syncMethod(BooleanLockTest.java:29)
	at com.today.roc.go.understand.thread.four.booleanlock.BooleanLockTest.lambda$interruptAble$1(BooleanLockTest.java:60)
	at java.lang.Thread.run(Thread.java:748)
 block thread : Thread[T2,5,] 
 T1  :2021-02-28 15:35:51.524 : release the lock  

获取锁超时

private static void getLockTimeout(BooleanLockTest blt) throws InterruptedException {
        new Thread(()->{
            blt.syncMethod(0);
        },"T1").start();
        TimeUnit.MILLISECONDS.sleep(2);
        Thread t2 = new Thread(() -> {
            blt.syncMethod(1000);
        }, "T2");
        t2.start();
    }


 block thread :  
 T1  :2021-02-28 15:56:04.566try lock  success  
 Thread[T1,5,main] get lock 8
 T2  :2021-02-28 15:56:05.570try lock  wait remainderMills :-3 
 java.util.concurrent.TimeoutException:  can not get lock during 1000 ms
	at com.today.roc.go.understand.thread.four.booleanlock.BooleanLock.lock(BooleanLock.java:86)
	at com.today.roc.go.understand.thread.four.booleanlock.BooleanLockTest.syncMethod(BooleanLockTest.java:34)
	at com.today.roc.go.understand.thread.four.booleanlock.BooleanLockTest.lambda$getLockTimeout$1(BooleanLockTest.java:66)
	at java.lang.Thread.run(Thread.java:748)
 block thread :  
 T1  :2021-02-28 15:56:12.571 : release the lock  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值