并发编程-线程通信

线程的通信

以下是学习中的一些笔记记录,如果有错误希望得到你的指正~ 同时也希望对你有点帮助~

wait/notify

  • 基于某个条件来等待或唤醒,这个条件就是通信的方式
  • wait/notify方法的调用必须要放在synchronize里面
  • 必须持有同一把锁,wait方法调用一定会让线程等待并释放锁

基于wait/notify实现生产者消费者模型

/**
 * 生产者模型
 */
public class ProducerModel implements Runnable{

    public Queue<String> bags;
    public int size;

    public ProducerModel(Queue<String> bags, int size) {
        this.bags = bags;
        this.size = size;
    }

    @Override
    @SneakyThrows
    public void run() {
        int i = 0;
        do {
            i++;
            //对共享变量加锁,实现互斥
            synchronized (bags){
                if(bags.size() == size){
                    System.out.println("bags is full....");
                    bags.wait();
                }
                TimeUnit.SECONDS.sleep(1);
                bags.add("bags" + i);
                System.out.println("producer produce bags" + i);
                bags.notify();
            }
        } while (true);
    }

}

/**
 * 消费者模型
 */
public class ConsumerModel implements Runnable{

    public Queue<String> bags;
    public int size;

    public ConsumerModel(Queue<String> bags, int size) {
        this.bags = bags;
        this.size = size;
    }

    @Override
    @SneakyThrows
    public void run() {
        do {
            synchronized (bags){
                if(bags.isEmpty()){
                    System.out.println("bags is empty....");
                    bags.wait();
                }
                TimeUnit.SECONDS.sleep(1);
                System.out.println("consumer consume " + bags.remove());
                //这是只是唤醒Producer,但是Producer并不能立马执行,必须得等同步代码块执行结束,也就是moniter exit执行完成才会抢占锁执行
                bags.notify();
            }
        } while (true);
    }
}

//测试类
public class ProducerConsumerTest {
    public static void main(String[] args) {
        Queue<String> queue = new LinkedList<>();
        ProducerModel producer = new ProducerModel(queue, 10);
        ConsumerModel consumer = new ConsumerModel(queue,10);
        Thread one = new Thread(producer);
        Thread two = new Thread(consumer);
        one.start();
        two.start();
    }
}

wait/notify实现原理

为什么wait/notify必须要用到synchronize?

  • 因为线程可能并行,所以需要一个互斥变量进行抢占,synchronize实现了互斥
  • wait/notify等待和唤醒需要队列来处理,synchronize提供了同步队列

通信原理图:
在这里插入图片描述

不管是WaitThread还是NotifyThread线程,都必须通过Monitor获得锁,当线程抢占锁失败之后,抢占锁失败的线程会被阻塞在SynchronizedQueue同步队列中,等到抢占到锁的线程释放之后,被阻塞的线程会从同步队列中唤醒。当WaitThread线程抢占到锁之后WaitThread线程中调用Object.wait()方法,然后会将WaitThread线程加入到WaitQueue等待队列(存储被wait方法阻塞的线程,因为存在可能多个线程同时调用同一个对象的wait方法)当中,此时WaitThread线程被阻塞并释放锁,这是NotifyThread线程抢占到锁,然后调用Object.notify或者Object.notifyAll方法,调用这俩方法的作用就是将等待队列中阻塞的线程移动到SynchroizedQueue同步队列中,区别就是移动一个阻塞线程还是所有阻塞线程

Join

join方法也是基于wait/notify来实现,notify是在run方法执行完,Jvm进行调用的

static void ensure_join(JavaThread* thread) {
	// We do not need to grap the Threads_lock, since we are operating on ourself.
	Handle threadObj(thread, thread->threadObj());
	assert(threadObj.not_null(), "java thread object must exist");
	ObjectLocker lock(threadObj, thread);
    // Ignore pending exception (ThreadDeath), since we are exiting anyway
	thread->clear_pending_exception();
	// Thread is exiting. So set thread_status field in java.lang.Thread class to
	TERMINATED.
	java_lang_Thread::set_thread_status(threadObj(),
	java_lang_Thread::TERMINATED);
	// Clear the native thread instance - this makes isAlive return false and
	allows the join()
	// to complete once we've done the notify_all below
	java_lang_Thread::set_thread(threadObj(), NULL);
    //Jvm在这唤醒被阻塞在Join()方法的所有线程
    lock.notify_all(thread);
	// Ignore pending exception (ThreadDeath), since we are exiting anyway
	thread->clear_pending_exception();
}

Condition

等价于wait/notify,condition是JUC包中实现的wait/notify

  • JUC中,锁的实现是Lock,锁的实现不同
  • wait/notify,锁的实现是synchronized
  • 相当于是Lock锁wait/notify的配套实现
/**
 * condition await == object wait
 *
 * @author zdp
 * @date 2022-05-27 15:48
 */
public class ConditionWait implements Runnable{

    private Lock lock;
    private Condition condition;

    public ConditionWait(Lock lock, Condition condition) {
        this.lock = lock;
        this.condition = condition;
    }

    @Override
    public void run() {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "  await() 开始...");
            condition.await();
            System.out.println(Thread.currentThread().getName() + "  await() 完成...");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}


/**
 * condition signal == object notify
 *
 * @author zdp
 * @date 2022-06-07 22:48
 */
public class ConditionNotify implements Runnable {
    private Lock lock;
    private Condition condition;

    public ConditionNotify(Lock lock, Condition condition) {
        this.lock = lock;
        this.condition = condition;
    }

    @Override
    public void run() {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "  notify 开始...");
            condition.signal();
            System.out.println(Thread.currentThread().getName() + "  notify 完成...");
        } finally {
            lock.unlock();
        }
    }

}


/**
 * 测试await、signal
 *
 * @author zdp
 * @date 2022-06-07 22:48
 */
public class ConditionWaitNotifyTest {
    public static void main(String[] args) throws InterruptedException{
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        Thread wait = new Thread(new ConditionWait(lock, condition));
        Thread notify = new Thread(new ConditionNotify(lock, condition));
        wait.start();
        TimeUnit.SECONDS.sleep(1);
        notify.start();
    }
}

Condition源码分析

await
  • 释放锁
  • 释放锁的线程应该被阻塞
  • 线程被阻塞之后要存储到队列中
  • 当阻塞线程被唤醒,要重新去竞争锁 → 走AQS的竞争锁逻辑
  • 要能够处理interrupt()的中断响应
class AbstractQueuedSynchronizer{
    //condition await
    public final void await() throws InterruptedException {
        if (Thread.interrupted()) {
            throw new InterruptedException();
        }
        //将当前线程封装为Node添加到等待队列(单向链表)
        Node node = addConditionWaiter();
        //添加到等待队列后,将持有释放
        int savedState = fullyRelease(node);
        int interruptMode = 0;
        //从尾向头查找当前线程节点是否在AQS队列中,没有在AQS中排队等待(释放了锁肯定不在AQS			 队列中),则通过LockSupport.park()阻塞前线程
        while (!isOnSyncQueue(node)) {
            //阻塞当前线程,(当其他线程调用signal()方法时,被唤醒的线程会从这里开始执行)			  上下文切换
            LockSupport.park(this);
            //要判断当前被阻塞的线程是否因为interrupt()唤醒,interrupt中断操作会唤醒处			  于等待状态下的线程,所以可能线程不是被single()方法唤醒而是被interrupt唤醒				 checkInterruptWhileWaiting()判断当前线程在阻塞期间是否被中断唤醒过
            if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                break;
        }
        //如果在AQS中(阻塞被唤醒后,阻塞线程将被signal()方法转移到AQS队列中),则尝试争抢		   锁,saveState表示释放锁前的重入次数
        if (acquireQueued(node, savedState) && interruptMode != THROW_IE) {
            interruptMode = REINTERRUPT;
        }
        // clean up if cancelled
        if (node.nextWaiter != null) {
            unlinkCancelledWaiters();
        }
        if (interruptMode != 0) {
            reportInterruptAfterWait(interruptMode);
        }
    }

    //将当前线程添加到等待队列,单项链表
    private Node addConditionWaiter() {
        Node t = lastWaiter;
        // If lastWaiter is cancelled, clean out.
        if (t != null && t.waitStatus != Node.CONDITION) {
            unlinkCancelledWaiters();
            t = lastWaiter;
        }
        //封装线程为Node节点,设置线程为 CONDITION 等待状态
        Node node = new Node(Thread.currentThread(), Node.CONDITION);
        //等待队列为空
        if (t == null){
            //将当前线程节点设为第一个节点
            firstWaiter = node;
        } else {
            //等待队列不为空,那么将当前线程节点加入到下一个节点(尾插法)
            t.nextWaiter = node;
        }
        //设置尾节点
        lastWaiter = node;
        return node;
    }

    //释放锁并返回锁的重入次数,返回重入次数是为了唤醒线程的时候使用
    final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            //获取重入次数
            int savedState = getState();
            //成功释放锁后(释放锁是AQS的逻辑),返回线程重入次数
            if (release(savedState)) {
                failed = false;
                return savedState;
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
            if (failed) {
                node.waitStatus = Node.CANCELLED;
            }
        }
    }

    //尝试争抢锁
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                //获取当前节点上一个节点
                final Node p = node.predecessor();
                //如果上一个节点是头节点,那么将会去争抢lock锁
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //上一个节点不是head节点,那么通过LockSupport.park() 阻塞线程,继续在AQS队列中等待
                if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) {
                    interrupted = true;
                }
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

}
signal
  • 要把被阻塞的线程先唤醒
  • 把等待队列中被唤醒的线程转移到AQS队列中
public final void signal() {
    if (!isHeldExclusively()){
        throw new IllegalMonitorStateException();
    }
    //获取当前等待队列,拿到头节点也就拿到了等待队列
    Node first = firstWaiter;
    //队列不为空,开始唤醒
    if (first != null) {
        doSignal(first);
    }
}

//唤醒等待队列中的一个线程
private void doSignal(Node first) {
    do {
        if ( (firstWaiter = first.nextWaiter) == null){
            lastWaiter = null;
        }
        first.nextWaiter = null;
    } while (!transferForSignal(first) && (first = firstWaiter) != null);
}

//
final boolean transferForSignal(Node node) {
    //因为节点初始状态为Condition,不过不能CAS成功,说明节点状态已经变更
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)){
        return false;
    }
	//将当前等待队列中的头部节点保存到AQS队列
    Node p = enq(node);
    int ws = p.waitStatus;
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)){
        LockSupport.unpark(node.thread);//唤醒线程,此时被唤醒线程已经到AQS队列中,然后被唤醒线程回到await()方法中被阻塞的地方继续执行
    }
    return true;
}

Condition的实际应用

  • condition实现阻塞队列
    • 线程池中会用到阻塞队列
    • 生产者消费者
    • 流量缓冲

condition实现阻塞队列

public class ConditionBlockedQueue {

    /**
     * 队列容器
     */
    private List<String> queue;

    /**
     * 容器已经存储的个数
     */
    private volatile int size;

    /**
     * 容器大小
     */
    private volatile int count;

    /**
     * Lock锁
     */
    private Lock lock = new ReentrantLock();

    /**
     * 等待队列
     */
    private Condition add = lock.newCondition();
    private Condition take = lock.newCondition();

    public ConditionBlockedQueue(int count){
        this.count = count;
        queue = new ArrayList<>(count);
    }

    /**
     * 存元素的方法
     */
    public void add(String item) throws InterruptedException {
        lock.lock();
        try{
            if(size == count){
                System.out.println("队列满了,请先等一会~");
                //阻塞存元素的线程
                add.await();
            }
            ++size;
            queue.add(item);
            TimeUnit.MICROSECONDS.sleep(10);
            //唤醒取元素的线程
            take.signal();
        } finally {
            lock.unlock();
        }

    }

    /**
     * 取元素的方法
     */
    public String take() throws InterruptedException {
        lock.lock();
        try {
            if (size == 0) {
                System.out.println("队列空了,请先等一会~");
                //阻塞取元素的线程
                take.await();
            }
            --size;
            String item = queue.remove(0);
            TimeUnit.MICROSECONDS.sleep(40);
            //唤醒存元素的线程
            add.signal();
            return item;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws Exception{
        ConditionBlockedQueue queue = new ConditionBlockedQueue(5);
        new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                try {
                    queue.add("item  " + i);
                    System.out.println("生产者生产元素item  " + i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(() -> {
            while (true) {
                try {
                    System.out.println("消费者消费元素  " + queue.take());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}
阻塞队列中的方法
  • 添加元素

    • add
      • 如果队列满了,抛出异常
    • offer
      • true/false:添加成功返回true,否则返回false
    • put
      • 如果队列满了,则一直阻塞
    • **offer **(timeout)
      • 带了超时时间,如果添加一个元素队列满了,此时会阻塞timeout时长,超过阻塞时长返回false
  • 移除元素

    • elemenet
      • 如果队列为空,抛出异常
    • peek
      • true/false,移除成功返回true,否则返回false
    • take
      • 如果队列为空,一直阻塞
    • poll(timeout)
      • 如果队列为空,在超时时间内还没有元素,则返回null
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值