线程协作机制

Java中多个线程之间如果需要协同工作,需要下面工具进行协同。我们分别来介绍下每个工具的作用和使用注意点。

读写锁ReentrantReadWriteLock

多个线程的读操作完全可以并行,在读多写少的场景中,让读操作并行可以明显提高性能,此时我们可以使用读写锁。

在Java并发包中,接口ReadWriteLock表示读写锁:

public interface ReadWriteLock {
  Lock readLock();
  Lock writeLock();
}

写锁:只有一个线程可以进行写操作:

  • 在获取写锁时,只有没有任何线程持有任何锁才可以获取到;
  • 在持有写锁时,其他任何线程都获取不到任何锁。

读锁:在没有其他线程持有写锁的情况下,多个线程可以获取和持有读锁。

即:只有“读-读”操作是可以并行的,“读-写” 和 “写-写” 都不可以。

ReentrantReadWriteLock是主要实现类,可重入读写锁。实现原理:

使用同一个整数变量表示锁的状态,16位给读锁用,16位给写锁用,使用一个变量便于进行CAS操作。

锁的等待队列也只有一个。

写锁的获取:确保当前没有其他线程持有任何锁,否则就等待。

写锁释放后:将等待队列中的第一个线程唤醒,唤醒的可能是等待读锁的,也可能是等待写锁的。

读锁的获取:首先,只要写锁没有被持有,就可以获取到读锁;此外,在获取到读锁后,它会检查等待队列,逐个唤醒最前面的等待读锁的线程,直到第一个等待写锁的线程。如果有其他线程持有写锁,获取读锁会等待。

读锁释放后:检查读锁和写锁数是否都变为了0,如果是,唤醒等待队列中的下一个线程。

信号量Semaphore

有的单个资源即使可以被并发访问,但并发访问数多了可能影响性能,所以希望限制并发访问的线程数。信号量类Semaphore就是用来解决这类问题的,它可以限制对资源的并发访问数:

public Semaphore(int permits)
public Semaphore(int permits, boolean fair)

permits表示许可数量,fire表示公平。主要方法:

//阻塞获取许可
public void acquire() throws InterruptedException
//阻塞获取许可,不响应中断
public void acquireUninterruptibly()
//批量获取多个许可
public void acquire(int permits) throws InterruptedException
public void acquireUninterruptibly(int permits)

//尝试获取
public boolean tryAcquire()
//限定等待时间获取
public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException

//释放许可
public void release()

信号量与锁的区别:

  • 一般锁只能由持有锁的线程释放,而Semaphore表示的只是一个许可数,任意线程都可以调用其release方法;
  • 主要的锁实现类ReentrantLock是可重入的,而Semaphore不是,每一次的acquire调用都会消耗一个许可。

实现原理:信号量的基本原理比较简单,也是基于AQS实现的,permits表示共享的锁个数,acquire方法就是检查锁个数是否大于0,大于则减一,获取成功,否则就等待,release就是将锁个数加一,唤醒第一个等待的线程。

倒计时门栓CountDownLatch

CountDownLatch相当于是一个门栓,一开始是关闭的,所有希望通过该门的线程都需要等待,然后开始倒计时,倒计时变为0后,门栓打开,等待的所有线程都可以通过,它是一次性的,打开后就不能再关上了

public CountDownLatch(int count)

public void await() throws InterruptedException
public boolean await(long timeout, TimeUnit unit) throws InterruptedException
public void countDown()

await检查计数是否为0,如果大于0,就等待,await可以被中断,也可以设置最长等待时间。

countDown检查计数,如果已经为0,直接返回,否则减少计数,如果新的计数变为0,则唤醒所有等待的线程。

门栓的两种应用场景:一种是同时开始,另一种是主从协作。

循环栅栏CyclicBarrier

CyclicBarrier相当于是一个栅栏,所有线程在到达该栅栏后都需要等待其他线程,等所有线程都到达后再一起通过,它是循环的,可以用作重复的同步。

CyclicBarrier特别适用于并行迭代计算,每个线程负责一部分计算,然后在栅栏处等待其他线程完成,所有线程到齐后,交换数据和计算结果,再进行下一次迭代。

public CyclicBarrier(int parties)
public CyclicBarrier(int parties, Runnable barrierAction)

parties表示参与的线程个数;

Runnable表示栅栏动作,当所有线程到达栅栏后,在所有线程执行下一步动作前,运行参数中的动作,这个动作由最后一个到达栅栏的线程执行

CyclicBarrier主要的方法就是await:

public int await() throws InterruptedException, BrokenBarrierException
public int await(long timeout, TimeUnit unit) throws InterruptedException,
	BrokenBarrierException, TimeoutException

await在等待其他线程到达栅栏。

调用await后,表示自己已经到达。

如果自己是最后一个到达的,就执行可选的命令,执行后,唤醒所有等待的线程,然后重置内部的同步计数,以循环使用。

await可以被中断,可以限定最长等待时间,中断或超时后会抛出异常。

异常BrokenBarrierException表示栅栏被破坏了:

  • 在CyclicBarrier中,参与的线程是互相影响的,只要其中一个线程在调用await时被中断了,或者超时了,栅栏就会被破坏。
  • 如果栅栏动作抛出了异常,栅栏也会被破坏。被破坏后,所有在调用await的线程就会退出,抛出BrokenBarrierException。

CyclicBarrier与CountDownLatch的区别:

  1. 线程角色
  2. CountDownLatch的参与线程是有不同角色的,有的负责倒计时,有的在等待倒计时变为0,负责倒计时和等待倒计时的线程都可以有多个,用于不同角色线程间的同步。
  3. CyclicBarrier的参与线程角色是一样的,用于同一角色线程间的协调一致。
  4. CountDownLatch是一次性的,而CyclicBarrier是可以重复利用的。

ThreadLocal

线程本地变量:每个线程都有同一个变量的独有拷贝。

ThreadLocal是一个泛型类,接受一个类型参数T,它只有一个空的构造方法,主要函数:

public T get()
public void set(T value)

protected T initialValue()
public void remove()

set就是设置值,get就是获取值,如果没有值,返回null。

initialValue用于提供初始值,这是一个受保护方法,可以通过匿名内部类的方式提供,当调用get方法时,如果之前没有设置过,会调用该方法获取初始值,默认实现是返回null。

remove删掉当前线程对应的值,如果删掉后,再次调用get,会再调用initialValue获取初始值。

ThreadLocal对象一般都定义为static,以便于引用。

基本原理:每个线程都有一个Map,类型为ThreadLocalMap,调用set实际上是在线程自己的Map里设置了一个条目,键为当前的ThreadLocal对象,值为value。ThreadLocalMap是一个内部类,它是专门用于ThreadLocal的,与一般的Map不同,它的键类型为WeakReference<ThreadLocal>。

#java原理##并发编程#
27届毕业生-Java知识专辑 文章被收录于专栏

知其然知其所以然,只有掌握了底层原理,借助第一性原理,才可以在日常开发和项目中运用自如,潇洒走江湖。 专为27届毕业生准备,托起您的就业梦。 该专辑会不定时更新,建议27届同学订阅,入职后扎实的基本功可以帮您争取更好的机会和项目。

全部评论
ThreadLocal是如何使用弱引用的?为什么要使用弱引用? https://www.nowcoder.com/discuss/859500996014141440
点赞 回复 分享
发布于 昨天 18:30 上海

相关推荐

头像
03-03 13:17
已编辑
苏州大学 Java
面试官真的很有耐心,人非常nice,但问得也是真的很细。面完半小后约HR面。有没有人说说HR面会问啥?【希望能过吧,以前真没想到面个试这么耗精力,这一周感觉都被掏空了】1.请做一下自我介绍。2.你掌握的数据结构有哪些?3.请讲一下一致性哈希的原理和解决的问题。4.请讲一下Ring&nbsp;buffer(环形缓冲区)的相关内容。5.请讲解一下HTTP状态码的相关分类和含义(如2xx、3xx、4xx、5xx)。6.请讲解一下四层网络负载均衡和七层网络负载均衡的区别,以及各自的应用场景。7.请讲一下反向代理的原理和常用工具,以及正向代理的相关内容。8.进程间通信的方式有哪些?哪种方式效率更高,为什么?9.请讲一下MySQL主从复制的实现原理(基于binlog、redolog相关)。10.多个从节点之间出现数据不一致的问题该如何解决?11.你了解的消息中间件有哪些?RabbitMQ、RocketMQ、Kafka这三种消息中间件的区别是什么?12.Redis中最常用的数据结构有哪些?13.请讲一下Redis中Zset(sorted&nbsp;set)的底层实现和优化策略。14.什么是小哈希和大哈希,二者在查找、插入性能上有什么区别?15.请讲一下TCC分布式事务算法的相关内容,以及它和2PC、3PC的区别。16.你在项目中使用的服务发现组件是什么,它的实现原理是什么?17.你在项目中使用的序列化协议是什么,为什么选择该协议?18.长连接的适用场景是什么?哪些场景不适合使用长连接,原因是什么?19.请设计一个评论系统(包括数据库表设计、数据结构、关联关系等)。20.【反问】想具体知道会做哪些模块的工作?有没有导师?
百特曼3:节子还是一如既往的八股大厂
查看78道真题和解析
点赞 评论 收藏
分享
评论
1
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务