JAVA并发编程(三)线程间的通信

本文详细介绍了Java中线程通信的多种方式,包括等待/通知机制、管道流、join()方法及ThreadLocal的使用。深入探讨了Synchronized、ReentrantLock、公平锁与非公平锁的工作原理和特性,以及读写锁的高效应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

volatile关键字的工作流程如下:
1)read和load阶段,把主存复制变量到当前线程工作内存。
2)use和assign阶段,执行代码,改变共享变量值。
3)store和write阶段,用工作内存数据刷新主存对应变量的值。

i++操作除了使用Synchronized关键字实现外,还可以使用AtomicInteger原子类进行实现。
原子类也并不完全安全,如果方法和方法之间的调用不是原子性的,也会导致线程安全问题。
Synchronized代码块有volatile同步的功能,关键字Synchronized可以使多个线程访问同一个资源具有同步性,它还具有将线程工作内存中的私有变量与公共内存中的变量同步的功能。

线程之间的通信:
等待/通知机制
方法wait()的作用是使当前执行代码的线程进行等待,wait()方法是Object类的方法,该方法将当前线程置入"预执行队列"中,并且在wait()所在代码处停止执行,知道接到通知或者被中断为止。

在调用wait()之前,线程必须获得该对象的对象级别锁,即只能在同步方法或者同步块中调用wait()方法。wait()执行后会释放锁。在从wait()返回前,线程会与其他线程竞争重新获得锁。

方法notify()也要在同步方法或同步块中调用,即在调用前,线程也必须获得该对象的锁。
该方法用来通知哪些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随机挑选出一个呈wait状态的线程,对其发出通知notify。

在执行了notify()方法后,当前线程不会马上释放该对象,呈wait状态的线程也不能马上获取该对象锁,要等到执行notify()方法的线程将程序执行完。

关键字Synchronized可以将任何一个Object对象作为同步对象来看待,而Java为每个Object都实现了wait()和notify()方法。

notifyAll()方法可以使所有正在等待队列中等待的同一共享资源的全部线程从等待状态退出,进入可运行状态,优先级最高的那个线程先执行,也有可能随机执行。

方法wait()被执行后会自动释放锁。方法notify()必须所在的Synchronized代码块执行完后才会释放锁。

当线程呈wait()状态时,调用线程对线过得interrupt()方法会出现InterruptedException异常。
方法notify()仅随机唤醒一个线程。当多次调用notify()方法时,会随机将等待wait状态的线程进行唤醒。

带一个参数的wait(long)方法的功能是等待某一时间内是否有线程对锁进行唤醒,如果超过这个时间则自动唤醒。

等待/通知模式最经典的案例就是”生产者/消费者“模式。原理都是基于wait/notify的。”假死“的现象其实就是线程进入WAITING等待状态,如果全部线程都进入等待状态,那么程序就不在执行任何业务了,整个项目呈停止状态。

多生产者与多消费者时需要解决假死问题,生产者唤醒了生产者,消费者唤醒了消费者的情况。解决方法就是把notify换成notifyAll。

通过管道进行线程间的通信:字节流
JAVA语言中提供了各种各样的输入/输出流Stream,使我们能够很方便地对数据进行操作,其中管道流(pipeStream)是一种特殊的流,用于在不同线程间直接传送数据。
一个线程发送数据到管道,另一个线程从管道中读取数据。在Java的JDK中提供了四个类来使线程间可以进行通信:
1)PipeInputStream和PipedOutputStream
2)PipedReader和PipedWriter
通过在线程中的管道的读取和写入可以轻松实现线程之间的通信。

主线程创建并启动子线程,如果子线程中要进行大量的耗时操作,主线程往往将早于子线程之前结束。如果主线程想等子线程执行完成之后在结束,比如主线程获取子线程的值,就要用到join()方法了。
join()的作用是等待线程对象销毁。Join()使所属的线程对象x正常执行run()方法中的任务,而使当前线程z进行无限期的阻塞,等待线程x销毁后在继续执行线程z后面的代码。

在join的过程中,如果当前线程对象被中断,则当前线程出现异常。对应的其他线程不受影响。

方法join(long)中可以设置参数的等待的时间。

方法join(long)的功能在内部是使用wait(long)方法来实现的,所以join(long)方法具有释放锁的特点。当执行wait(long)方法后,当前线程的锁被释放,那么其他线程就可以调用此线程中的同步方法了。

ThreadLocal的使用
变量值的共享可以使用public static变量的形式,所有线程都使用同一个public static变量。类ThreadLocal主要是解决的就是每个线程都绑定自己的值,可以将ThreadLocal类比喻成全局存放数据的盒子,盒子中可以存储每个线程的私有数据。

在这里插入图片描述
多个线程同时向t1对象中set()数据值,但每个线程还是能取出自己的数据。

使用类InheritableThreadLocal可以在子线程中取得父线程继承下来的值,同时允许在子线程中修改这个值。如果子线程在取得值的同时,主线程将InheritableThreadLocal中的值进行更改,那么子线程拿到的还是 旧的值。

ReentrantLock类
在Java多线程中,可以使用Synchronized关键字来实现线程之间的同步互斥,在JDK1.5新增加的ReentrantLock也能达到同样的效果。除此之外,还具有嗅探锁定,多路分支通知的功能。

在这里插入图片描述
调用ReentrantLock对象的lock()方法获取锁,调用unlock()方法释放锁。

ReentrantLock对象调用lock.lock()代码的线程持有了"对象监视器",其他线程只有等待锁被释放时再次增强,效果和Synchronized关键字一样,线程之间还是顺序执行的。

关键字Synchronized与wait()和notify()/notifyAll()方法相结合可以实现等待/通知模式,类ReentrantLock也可以实现同样的功能。但是需要借助Condition对象。借助Condition可以实现多路通知功能。

ReentrantLock结合Condition类结合可以实现选择性通知。调用condition对象的await()方法后,当前执行任务的线程就进入了等待状态。
在这里插入图片描述
Condition中的await和signal,signalAll相当于Object类的wait,notify和notifyAll方法。

如果想要实现分别唤醒不同的线程,需要定义多个condition对象。ReentrantLock实现生产者消费者模式与wait(),notify()相同。

公平锁与非公平锁
锁Lock分为公平锁与非公平锁,公平锁表示线程获取锁的顺序是按照线程加锁的顺序来分配的,先进先出。非公平说就是一种获取锁的抢占机制,是随机获得锁的(先来的不一定先得到)。

getHoldCount()的作用是查询当前线程保持此锁定的个数,也就是调用lock()方法的次数。

getQueueLength()的作用是返回正等待获取此锁定的线程的估计数。
getWaitQueueLength(Condition condition)的作用是返回等待与此锁定相关的给定条件Condition的线程估计数。
hasQueuedThread(Thread thread)的作用是查询指定的线程是否正在等待获取此锁定。
hasQueuedThreads()查询是否有线程正在等待获取此锁定。
hasWaiters的作用是查询是否有线程正在等待与此锁定有关的condition条件。
isFair()用来判断是不是公平锁。ReentrantLock默认是非公平锁。
isHeldByCurrentThread()查询当前线程是否保持此锁定。
isLocked()的作用是查询此锁定是否由任意线程保持。

lockInterruptibly()的作用是如果当前线程未被中断,则获取锁定,如果已被中断则出现异常。
tryLock()的作用是,仅在调用时锁定未被另一个线程保持的情况下才获取该锁定。
在这里插入图片描述
tryLock(long timeout, TimeUnit unit)的作用是,如果锁定在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁。
在这里插入图片描述
方法awaitUninterruptibly()接收中断后不抛出异常。

awaitUntil()线程在等待时间到达前,可以被其他线程提前唤醒。
利用condition的准确唤醒可以实现循环打印等功能。

ReentrantLock具有完全互斥排他的效果,同一时间只有一个线程在执行ReentrantLock.lock()方法后面的任务。这样虽然保证了实例变量的线程安全性,但是效率确实非常低下的。JDK中提供了一种读写锁ReentrantReadWriteLock类,可以加快运行效率。

这个读写锁表示有两个锁,一个是读相关的锁,称为共享锁,另一个是写相关的锁,也叫排他锁。读锁之间不互斥,读写锁互斥,写写锁互斥。
lock.readLock().lock();获得读锁,允许多个线程同时获取。
lock.writeLock().lock();获取写锁,同一时间只允许一个线程执行lock()方法后面的代码。
读写互斥。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值