Java多线程并发(终篇)


前言

本章主要介绍Java多线程的一些扩展知识。


十八、ThreadLocal作用

ThreadLocal,很多地方叫做线程本地变量,也有些地方叫做线程本地存储,ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。
ThreadLocalMap(线程的一个属性)

  1. 每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。
  2. 将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。
  3. ThreadLocalMap其实就是一个线程里面的一个属性,它在Thread类中定义:ThreadLocal.ThreadLocalMap threadLocals = null。

十九、锁的区别

synchronized和ReentrantLock的区别

  • 共同点
  1. 都是用来协调多线程对共享对象,变量的访问。
  2. 都是可重入锁,同一线程可以多次获得同一个锁。
  3. 都保证了可见性和互斥性。
  • 不同点
synchronizedReentrantLock
隐式获得、释放锁显示的获得、释放锁
不可以响应中断可以响应中断、可轮回
是JVM级别的是API级别
不公平锁可以实现公平锁
只有一个条件队列可以有多个条件队列
同步阻塞,悲观锁同步非阻塞,乐观锁
是Java的关键字是一个Lock接口
发生异常时会自动释放锁发生异常时,通常需要在finally控制块释放锁
无法知道是否获取锁可以知道是否获取到锁
无法提供同时读可以提供多个线程读操作的效率

二十、ConcurrentHashMap并发

20.1. 减小锁粒度

减小锁粒度是指缩放锁定对象的范围,从而减小锁冲突的可能性,从而提供系统的并发能力。减小锁粒度是一种削弱多线程竞争的有效手段,这种技术典型的应用是ConcurrentHashMap类的实现。对应HashMap而言,最重要的两个方法是get和set方法,如果我们对整个HashMap加锁,可以得到线程安全的对象,但是加锁粒度太大。Segment的大小也被称为ConcurrentHashMap的并发度。

20.2. 分段锁

ConcurrentHashMap,它内部细分了若干个小的HashMap,称之为段。默认情况下一个ConcurrentHashMap被进一步细分为16个段,即就是所得并发度。
如果需要在ConcurrentHashMap中添加一个新的表项,并不是将整个HashMap加锁,而是首先根据hashCode得到该表项应该存放在哪个段中,然后对该段加锁,并完成put操作。在多线程环境中,如果多个线程同时进行put操作,只要被加入的表项不存放在同一段中,则线程间可以做到真正的并行。

ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成
ConcurrentHashMap里包含了一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构,一个Segment里面包含一个HashEntry数组,每个HashEntry是一个链表结构的元素,每个Segment守护一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。
在这里插入图片描述

二十一、线程调度

21.1. 抢占式调度

抢占式调度指的是每条线程执行的时间、线程的切换都由系统控制,系统控制指的是在系统某种运行机制下,可能每条线程都分同样的执行时间片,也可能是某些线程执行的时间片较长,甚至某些线程得不到执行的时间片。在这种机制下,一个线程的堵塞不会导致整个进程堵塞。

21.2. 协同式调度

协同式调度指某一线程执行完后主动通知系统切换到另一线程上执行,这种模式就像接力赛一样,一个人跑完传递给下一个人。线程的执行时间由线程本身控制,线程切换可以预知,不存在多线程同步问题,但它有一个致命弱点:如果一个线程编写有问题,运行到一半就一直堵塞,那么可能导致整个系统奔溃。

21.3. JVM的线程调度实现

JVM使用的线程调度使用抢占式调度,Java线程会按优先级分配CPU时间片运行,且优先级越高越优先执行,但优先级高不代表能独自占用执行时间片,可能是优先级高得多越多的执行时间片,反之,优先级低的分到的执行时间少但不会分配不到执行时间。

二十二、进程调度算法

22.1. 优先调度算法

22.1.1. 先来先服务调度算法(FCFS)

当在作业调度中采用该算法,每次调度都是从后备作业队列中选择一个或多个最先进入该队列的作业,将它们调入内存,为它们分配资源、创建进程,然后放入就绪队列。

22.1.2. 短作业优先调度算法

短作业优先的调度算法是从后备队列中选择一个或若干个估计运行时间最短的作业,将它们调入内存运行。而短进程优先调度算法则是从就绪队列中选出一个估计运行时间最短的进程,将处理机分配给它,使他立即执行并一直执行到完成。

22.2. 高优先权优先调度算法

为了照顾紧迫型作业吗,使之在进入系统后便获得优先处理,引入了最高优先权优先调度算法。

22.2.1. 非抢占式优先权算法

在这种方式下,系统一旦把处理机分配给就绪队列中优先权最高的进程后,该进程便一直执行下去,直至完成;或因发生某事事件使该进程放弃处理机时。

22.2.2. 抢占式优先权调度算法

在这种方式下,系统同样是把处理机分配给优先权最高的进程,使之执行。但在其执行期间,只要又出现了另一个其优先权更高的进程,进程调度程序就立即停止当前进程的执行,重新将处理机分配给新到的优先权更高的进程。

22.3. 基于时间片的轮转调度算法

22.3.1. 基于时间片的轮转调度算法

在早期的时间片轮转法中,系统将所有的就绪进程按先来先服务的原则排成一个队列,每次调度时,把CPU分配给对首进程,并令其执行一个时间片。当执行的时间片用完时,由一个计时器发出时钟中断请求,调度程序便据此信号来停止该进程的执行,并将它送往就绪队列的末尾;然后,再把处理机分配给就绪队列中新的对首进程,同时也让它执行一个时间片。

二十三、CAS

23.1. 概念以及特性

CAS(Compare And Set)比较并交换,CAS算法的过程是这样的:它包含3个参数CAS(V,E,N)。V表示要更新的变量,E表示预期值,N表示新值。当且仅当V值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。最后,CAS返回当前V的真实值。

23.2. ABA问题

CAS会导致“ABA问题”。CAS算法实现一个重要前提需要取出内存中某时刻的数据,而在下时刻比较并替换,那么在这个时间差类会导致数据的变化。

二十四、AQS

AbstractQueuedSynchronizer类如其名,抽象的队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch。

它维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列。这里volatile是核心关键词,具体volatile的语义,在此不述。state的访问方式有三种:
getState()
setState()
compareAndSetState()
AQS定义两种资源共享方式

  • Exclusive独占资源-ReentrantLock
    Exclusive(独占,只有一个线程能执行)
  • Share共享资源-Semaphore/CountDownLatch
    Share(共享,多个线程可同时执行)

AQS只是一个框架,具体资源的获取/释放方式交由自定义同步器去实现,AQS这里只定义了一个接口,具体资源的获取交由自定义同步器去实现了之所以没有定义成abstract,是因为独占模式下只用实现tryAcquire-tryRelease,而共享模式下只用实现tryAcquireShared-tryReleaseShared。如果都定义成abstract,那么每个模式也要去实现另一模式下的接口。不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护,AQS已经在顶层实现好了。自定义同步器实现时主要实现以下几种方法:

  1. isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要实现它。
  2. tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
  3. tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
  4. tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败,0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
  5. tryReleaseShared(int):共享方式。尝试释放资源。如果释放后运行唤醒后续等待节点返回true,否则返回false。

同步器的实现是ABS核心
同步器的实现是ABS核心,以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1.此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0为止,其他线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的,这就是可重入的概念。但是要注意,获取多少次就要释放多少次,这样才能保证state是能回到零态的。


总结

以上便是Java多线程扩展知识。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值