Java多线程

9 篇文章 0 订阅
7 篇文章 0 订阅

1、多个生产者与多个消费者时出现“假死”问题
由于有多个消费者的存在,如果释放锁是使用notify(),即随机释放一个对象锁,有可能释放的是一个同类的锁。
场景:

  1. 生产者1线程生产了一条消息,释放一个锁
  2. 生产者1线程再次获得锁马上进入发现已经有数据,进入等待状态
  3. 生产者2线程进入发现已经有数据了,进入等待状态
  4. 消费者2线程运行消费了一条消息,释放一个锁
  5. 消费者2线程再次获得锁,发现没数据,进入等待状态
  6. 消费者1线程运行,发现没数据,进入等待状态
  7. 生产者1线程检测到没数据了,生产一个数据,释放一个锁
  8. 生产者1线程再次进入发现已经有数据,进入等待状态
  9. 生产者2线程进入发现已经有数据,进入等待状态

解决:用notifyAll();释放所有的线程锁

2、 多线程之间的管道通讯(字节流)
两条线程之间使用 PipeInputStream与PipeOutputStream进行数据的交流
使用connect()把输入管道与输出管道连接起来

3、多线程之间的管道通讯(字符流)
两条线程之间使用PipedReader与PipedWriter进行数据的交流
使用connect()把输入管道与输出管道连接起来

4、使用join使线程排队运行,有些类似同步的运行效果,join与synchronized的区别是:join在内部使用wait()方法等待,而synchronized关键字使用的是“对象监控器”

5、ThreadLocal修饰的static变量,可以令各个线程都获取回自己属于自己线程的变量

6、InheritableThreadLocal可以让线程变量的值继承,让子线程获得的值与父线程的一样,并且可以在继承的同时对子线程的值进行修改

7、ReentrantLock实现同步,与synchronized效果一样,ReentrantLock配合Condition可以实现“选择性通知”,而synchronized的wait(),notify(),notifyALL()只能随机通知一个,要么全部通知。使用conditon之前一定要先使用lock,否则会抛出IllegalMonitorStateException异常

8、getHoldCount()、getQueueLength()、getWaitQueueLength()用法
getHoldCount()是查询当前线程保持此锁的个数
getQueueLength()返回正等待获取此锁定的线程估计数
getWaitQueueLength()返回等待与此锁定相关的给定条件Condition的线程估计数

9、hasQueuedThread()、hasQueuedThreads()、hasWaiters()用法
hasQueuedThread()查询指定的线程是否正在等待获取此锁定
hasQueuedThreads()查询是否有线程正在等待获取此锁定
hasWaiters()查询是否有线程正在等待与此锁定有关的condition条件

10、isFair()、isHeldByCurrentThread()、isLocked()用法
isFair()判断是不是公平锁
isHeldByCurrentThread()查询当前线程是否保持此锁定
isLocked()查询此锁定是否由任意线程保持

11、awaitUntil()用法
相当于await(),指定睡眠的时间,期间可以被其它线程唤醒

12、ReentrantReadWriteLock读写锁
“读写”、“写读”、“写写”互斥
“读读”异步非互斥

13、schedule如果执行任务的时间被延时,那么下一次任务的执行时间以上一次任务结束时的时间为参考来计算
scheduleAtFixedRate如果执行任务的时间没有被延时,则下一次执行任务的时间是上一次任务的开始时间加上delay时间
scheduleAtFixedRate如果执行任务的时间被延时,那么下一次任务的执行时间以上一次任务结束时的时间为参考来计算

14、利用DLC双重检查锁来解决“懒汉模式”遇到多线程的问题

15、静态内置类实现单例模式可避免多线程问题

16、序列化与反序列化实现单例模式避免多线程问题,readresolve会自动触发以确保反序列化的对象是原来的那个对象,确保单例

17、ThreadPoolExecutor执行器的处理流程:
(1)当线程池大小小于corePoolSize就新建线程,并处理请求.
(2)当线程池大小等于corePoolSize,把请求放入workQueue中,池子里的空闲线程就去从workQueue中取任务并处理.
(3)当workQueue放不下新入的任务时,新建线程加入线程池,并处理请求,如果池子大小撑到了maximumPoolSize就用RejectedExecutionHandler来做拒绝处理.
(4)另外,当线程池的线程数大于corePoolSize的时候,多余的线程会等待keepAliveTime长的时间,如果无请求可处理就自行销毁.

18、Semaphore类通常用于限制可以访问某些资源(物理或逻辑的)线程数目。acquire获得许可后,许可量减少;release释放许可后,许可量增加。当许可量为零时,线程阻塞。

19、什么是Leader/Followers模式

  • 有若干个线程(一般组成线程池)用来处理大量的事件
  • 有一个线程作为领导者,等待事件的发生;其他的线程作为追随者,仅仅是睡眠。
  • 假如有事件需要处理,领导者会从追随者中指定一个新的领导者,自己去处理事件。
  • 唤醒的追随者作为新的领导者等待事件的发生。
  • 处理事件的线程处理完毕以后,就会成为追随者的一员,直到被唤醒成为领导者。
  • 假如需要处理的事件太多,而线程数量不够(能够动态创建线程处理另当别论),则有的事件可能会得不到处理。

所有线程会有三种身份中的一种:leader和follower,以及一个干活中的状态:proccesser。它的基本原则就是,永远最多只有一个leader。而所有follower都在等待成为leader。线程池启动时会自动产生一个Leader负责等待网络IO事件,当有一个事件产生时,Leader线程首先通知一个Follower线程将其提拔为新的Leader,然后自己就去干活了,去处理这个网络事件,处理完毕后加入Follower线程等待队列,等待下次成为Leader。这种方法可以增强CPU高速缓存相似性,及消除动态内存分配和线程间的数据交换。

20、JDK5.0新增的concurrent包里面的BlockingQueue类用作阻塞队列,通常是一个业务流程里面,有些需要实时响应,而有些则没有要求那么高,所以可以通过这种阻塞队列实现的生产者/消费者模式来达到缓冲的效果,这个工具类主要是优化了之前实现多线程时使用wait()、notify()常常需要顾及到多线程并发时会出现的各种问题,比如造成死锁。
阻塞队列的成员有:

队列有界性数据结构
ArrayBlockingQueuebounded(有界)加锁arrayList
LinkedBlockingQueueoptionally-bounded加锁linkedList
PriorityBlockingQueueunbounded加锁heap
DelayQueueunbounded加锁heap
SynchronousQueuebounded加锁
LinkedTransferQueueunbounded加锁heap
LinkedBlockingDequeunbounded无锁heap
  1. ArrayBlockingQueue:是一个用数组实现的有界阻塞队列,此队列按照先进先出(FIFO)的原则对元素进行排序。支持公平锁和非公平锁。【注:每一个线程在获取锁的时候可能都会排队等待,如果在等待时间上,先获取锁的线程的请求一定先被满足,那么这个锁就是公平的。反之,这个锁就是不公平的。公平的获取锁,也就是当前等待时间最长的线程先获取锁】
  2. LinkedBlockingQueue:一个由链表结构组成的有界队列,此队列的长度为Integer.MAX_VALUE。此队列按照先进先出的顺序进行排序。
  3. PriorityBlockingQueue:一个支持线程优先级排序的无界队列,默认自然序进行排序,也可以自定义实现compareTo()方法来指定元素排序规则,不能保证同优先级元素的顺序。
  4. DelayQueue:一个实现PriorityBlockingQueue实现延迟获取的无界队列,在创建元素时,可以指定多久才能从队列中获取当前元素。只有延时期满后才能从队列中获取元素。(DelayQueue可以运用在以下应用场景:1.缓存系统的设计:可以用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从DelayQueue中获取元素时,表示缓存有效期到了。2.定时任务调度。使用DelayQueue保存当天将会执行的任务和执行时间,一旦从DelayQueue中获取到任务就开始执行,从比如TimerQueue就是使用DelayQueue实现的。)
  5. SynchronousQueue:一个不存储元素的阻塞队列,每一个put操作必须等待take操作,否则不能添加元素。支持公平锁和非公平锁。SynchronousQueue的一个使用场景是在线程池里。Executors.newCachedThreadPool()就使用了SynchronousQueue,这个线程池根据需要(新任务到来时)创建新的线程,如果有空闲线程则会重复使用,线程空闲了60秒后会被回收。
  6. LinkedTransferQueue:一个由链表结构组成的无界阻塞队列,相当于其它队列,LinkedTransferQueue队列多了transfer和tryTransfer方法。
  7. LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。队列头部和尾部都可以添加和移除元素,多线程并发时,可以将锁的竞争最多降到一半。

21、偏向锁的获取过程(假设开启了偏向锁优化):
(1)初始时对象处于biasable状态,并且ThreadID为0即biasable & unbiased状态(这里不讨论epoch和age)
(2)当一个线程试图锁住一个处于biasable & unbiased状态的对象时,通过一个CAS将自己的ThreadID放置到Mark Word中相应的位置,如果CAS操作成功进入第(3)步否则进入(4)步
(3)当进入到这一步时代表当前没有锁竞争,Object继续保持biasable状态,但是这时ThreadID字段被设置成了偏向锁所有者的ID,然后进入到第(6)步
(4)当前线程执行CAS获取偏向锁失败(这一步是偏向锁的关键),表示在该锁对象上存在竞争并且这个时候另外一个线程获得偏向锁所有权。当到达全局安全点(safepoint)时获得偏向锁的线程被挂起,并从偏向锁所有者的私有Monitor Record列表中获取一个空闲的记录,并将Object设置为LightWeight Lock状态并且Mark Word中的LockRecord指向刚才持有偏向锁线程的Monitor record,最后被阻塞在安全点的线程被释放,进入到轻量级锁的执行路径中,同时被撤销偏向锁的线程继续往下执行同步代码。
(5)当一个线程试图锁住一个处于biasable & biased并且ThreadID不等于自己的ID时,这时由于存在锁竞争必须进入到第(4)步来撤销偏向锁。
(6)运行同步代码块

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值