线程的通信
文章目录
以下是学习中的一些笔记记录,如果有错误希望得到你的指正~ 同时也希望对你有点帮助~
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
- add
-
移除元素
- elemenet
- 如果队列为空,抛出异常
- peek
- true/false,移除成功返回true,否则返回false
- take
- 如果队列为空,一直阻塞
- poll(timeout)
- 如果队列为空,在超时时间内还没有元素,则返回null
- elemenet