一般网站建设需要哪些东西,wordpress会员小图标,贵州建设监理协会网站,买了vps后怎么安装Wordpress文章目录前言操作系统——进程和线程进程进程组成进程状态进程控制进程创建进程终止进程阻塞和唤醒进程通信线程线程组成线程状态线程控制线程的实现方式用户线程内核线程混合方式CPU调度调度的层次调度的实现调度器调度的时机、切换与过程进程调度的方式闲逛进程两种线程的调度…
文章目录前言操作系统——进程和线程进程进程组成进程状态进程控制进程创建进程终止进程阻塞和唤醒进程通信线程线程组成线程状态线程控制线程的实现方式用户线程内核线程混合方式CPU调度调度的层次调度的实现调度器调度的时机、切换与过程进程调度的方式闲逛进程两种线程的调度进程切换同步与互斥实现临界区互斥的基本方法互斥锁管程Java并发模型线程组成线程状态线程控制线程创建任务线程池线程池的构造工作流程钩子方法队列的维护终结预定义线程池线程中断线程阻塞线程安全JMM不可变对象ThreadLocalUnsafe类解析volatile原子类对象锁对象锁的实现管程实现方式锁记录实现方式线程通信AQS状态变量等待队列AQS的实现流程AQS的使用流程线程通信JDK中AQS的实现集合安全CopyOnWriteArrayListConcurrentHashMapConcurrentSkipListMapBlockingQueue前言
本文分为两个部分第一部分是操作系统中进程和线程的知识第二部分是Java中进程和线程的知识。之所以这么划分是因为Java是运行在JVM之上的而JVM就等价于Java的操作系统。如果只从Java层面学习那么始终是知其然而不知其所以然只有通过对比学习才能彻底地接受、理解和掌握。
操作系统——进程和线程
进程
进程组成
在多程序环境下允许多个程序并发执行为了更好地描述和控制程序的并发执行引入了进程的概念。进程是一个独立的运行单位也是操作系统进行资源分配和调度的基本单位。它由以下三部分组成
进程控制块PCBPCB是一个专门的数据结构。系统利用PCB来描述进程的基本情况和运行状态进而控制和管理进程。所谓创建进程就是创建进程的PCB而撤销进程就是撤销进程的PCB。PCB中包含的主要内容如下
进程描述信息进程控制和管理信息资源分配清单处理及相关信息进程标识符PID进程当前状态代码段指针通用寄存器值用户标识符UID进程优先级数据段指针地址寄存器值代码运行入口地址堆栈段指针控制寄存器值程序的外存地址文件描述符标志寄存器值进入内存时间键盘状态字CPU占用时间鼠标信号量使用
程序段程序段就是能被进程调度程序调度到CPU执行的程序代码段多个进程可以共享同一个程序段。数据段一个进程的数据段可以是进程对应的程序加工处理的原始数据也可以是程序执行时产生的中间或最终结果。
进程具有的特征如下
动态性进程是程序的一次执行它有创建、活动、暂停、终止等过程具有一定的生命周期是动态产生、变化和消亡的。并发性指多个进程实体同存于内存中能在一段时间内同时运行。引入进程的目的就是使进程能和其它进程并发执行。独立性指进程实体是一个能独立运行、独立获得资源和独立接受调度的基本单位。凡未建立PCB的程序都不能作为一个独立的单位参与运行。异步性由于进程的相互制约使得进程按各自独立的、不可预知的速度向前推进。异步性会导致执行结果的不可再现性为此在操作系统中必须配置相应的进程同步机制。
进程状态
由于系统中各进程之间的相互制约及系统运行环境的变化使得进程的状态也在不断地发生变化。通常进程有以下5种状态
运行态进程正在计算机上运行。在单核计算机中每个时刻只有一个进程处于运行态。就绪态进程获得了除CPU外的一切所需资源一旦得到CPU便可立即运行。系统中处于就绪态的进程可能有多个通常将它们排成一个队列称为就绪队列。阻塞态进程正在等待某一事件如等待某资源为可用或等待I/O完成而暂停运行即使CPU空闲该进程也不能运行。系统通常将处于阻塞态的进程也排成一个队列称为阻塞队列。甚至根据阻塞原因的不同设置多个阻塞队列。创建态创建进程需要多个步骤首先申请一个空白PCB并向PCB中填写用于控制和管理进程的信息然后为该进程分配运行时所必须的资源最后把该进程转入就绪态并插入就绪队列。但是如果进程所需的资源尚不能得到满足如内存不足则创建工作尚未完成进程此时所处的状态就是创建态。结束态进程正从系统中消失可能是进程正常结束或其它原因退出运行。进程需要结束运行时系统首先将该进程置为结束态然后进一步处理资源释放和回收等工作。
前三种是进程的基本状态它们之间的状态转换如下
就绪态→\rightarrow→运行态处于就绪态的进程被调度后获得CPU于是进程由就绪态转换为运行态。运行态→\rightarrow→就绪态处于运行态的进程在时间片用完后不得不让出CPU从而进程由运行态转换为就绪态。此外在可剥夺的操作系统中当有更高优先级的进程就绪时调度程序将正在执行的进程转换为就绪态让更高优先级的进程执行。运行态→\rightarrow→阻塞态主动进程请求某资源的使用和分配或等待某一事件的发生时它就从运行态转换为阻塞态。阻塞态→\rightarrow→就绪态被动进程等待的事件到来时中断处理程序必须把相应进程的状态由阻塞态转换为就绪态。 进程控制
进程控制的主要功能是对系统中的所有进程实施有效的管理它具有创建新进程、撤销已有进程、实现进程状态转换等功能。在操作系统中一般把进程控制用的程序段称为原语原语的特点是执行期间不允许中断它是一个不可分割的基本单位。
进程创建
操作i系统允许一个进程创建另一个进程此时创建者称为父进程被创建的进程称为子进程。子进程可以继承父进程所拥有的资源。当子进程被撤销时应将其从父进程那里获得的资源归还给父进程。此外在撤销父进程时通常也会同时撤销其所有的子进程。操作系统创建一个新进程的过程如下创建原语
为新进程分配一个唯一的进程标识号并申请一个空白PCBPCB是有限的。若PCB申请失败则创建失败。为进程分配其运行所需的资源如内存、文件、I/O设备和CPU时间等在PCB中体现。这些资源或从操作系统获得或仅从其父进程获得。如果资源不足则并不是创建失败而是处于创建态等待资源。初始化PCB主要包括初始化标志信息、初始化计算机状态信息和初始化计算机控制信息以及设置进程的优先级等。若进程就绪队列能够接纳新进程则将新进程插入就绪队列等待被调度运行。
进程终止
操作系统终止进程的过程如下终止原语
根据被终止进程的标识符检索出该进程的PCB从中读出该进程的状态。若要被终止进程处于执行状态立即终止该进程的执行并将计算机资源分配给其它进程。若该进程还有子孙进程则应将其所有子孙进程终止。将该进程所拥有的全部资源或归还给其父进程或操作系统。将该PCB从所在队列中删除。
进程阻塞和唤醒
正在执行的进程由于期待的某些事件未发生进程便通过调用阻塞原语使自己由运行态变为阻塞态。阻塞原语的执行过程如下:
找到将要被阻塞进程的标识号对应的PCB。若该进程为运行态则保护其现场将其状态转为阻塞态停止运行。把该PCB插入相应事件的等待队列将处理机资源调度给其它就绪进程。
当被阻塞进程所期待的事件出现时由有关进程调用唤醒原语将等待该事件的进程唤醒。唤醒原语的执行过程如下:
在该事件的等待队列中找到相应进程的PCB。将其从等待队列中移出并置其状态为就绪态。把该PCB插入就绪队列等待调度程序调度。
注意阻塞原语和唤醒原语是一对作用刚好相反的原语必须成对使用。如果在某进程中调用了阻塞原语则必须在与之合作的或其它相关的进程中安排一条相应的唤醒原语以便唤醒阻塞进程。否则阻塞进程将会因不能被唤醒而永久地处于阻塞状态。
进程通信
进程通信是指进程之间的信息交换常用的高级通信方式有以下三种
共享存储在通信的进程之间存在一块可直接访问的共享空间通过对这片共享空间进行读写操作实现进程之间的信息交换。在对共享空间进行读写操作时需要使用同步互斥工具进行控制。共享存储又分为两种低级方式的共享是基于数据结构的共享高级方式的共享则是基于存储区的共享。操作系统只负责为通信进程提供可共享使用的存储空间和同步互斥工具而数据交换则由用户自己安排读写指令完成。消息传递在消息传递系统中进程间的数据交换以格式化的消息为单位。若通信的进程之间不存在可直接访问的共享空间则必须利用操作系统提供的消息传递方法实现进程通信。进程通过系统提供的发送消息和接收消息两个原语进行数据交换。这种方式隐藏了通信实现细节使通信过程对用户透明简化了通信程序的设计是当前应用最广泛的进程间通信机制。消息传递的方式有以下两种 直接通信方式发送进程直接把消息发送给接收进程并将它挂在接收进程的消息缓冲队列上接收进程从消息缓冲队列中取得消息。间接通信方式 发送进程把消息发送到某个中间实体接收进程从中间实体取得消息。这种中间实体一般称为信箱。该通信方式广泛应用于计算机网络中。 管道通信管道通信是消息传递的一种特殊方式。所谓管道是指用于连接一个读进程和一个写进程以实现它们之间通信的一个共享文件又名pipe文件。向管道提供输入的发送进程以字符流形式将大量的数据送入管道而接收管道输出的接收进程则从管道中接收数据。
线程
线程组成
引入线程的目的是减小程序在并发执行时所付出的时空开销。线程是进程中的一个实体是被系统独立调度和分派的基本单位线程自己不拥有系统资源只拥有一点在运行中必不可少的资源但它可与同属一个进程的其它线程共享进程所拥有的全部资源。与进程类似系统也为每个线程配置一个线程控制块TCB用于记录控制和管理线程的信息。TCB通常包括
线程标识符。一组寄存器包括程序计数器、状态寄存器和通用寄存器。线程运行状态用于描述线程正处于何种状态。优先级。线程专有存储区线程切换时用于保存现场等。堆栈指针用于过程调用时保存局部变量及返回地址等。
引入线程后进程的内涵发生了改变进程只作为除CPU外的系统资源的分配单元而线程则作为CPU的分配单元。由于一个进程内部有多个线程若线程的切换发生在同一个进程内部则只需要很少的时空开销。引入线程操作系统的变化如下
调度在传统的操作系统中拥有资源和独立调度的基本单位都是进程每次调度都要进行上下文切换开销较大。在引入线程的操作系统中线程是独立调度的基本单位。在同一进程中线程的切换不会引起进程切换。但从一个进程中的线程切换到另一个进程中的线程时会引起进程切换。并发性在引入线程的操作系统中不仅进程之间可以并发执行而且一个进程中的多个线程之间亦可并发执行甚至不同进程中的线程也能并发执行。拥有资源进程是系统中拥有资源的基本单位而线程不拥有系统资源但线程可以访问其隶属进程的系统资源这主要表现在属于同一进程的所有线程都具有相同的地址空间。独立性每个进程都拥有独立的地址空间和资源除了共享全局变量不允许其它进程访问。某进程中的线程对其它进程不可见。同一进程中的不同线程是为了提高并发性及进行相互之间的合作而创建的它们共享进程的地址空间和资源。系统开销在创建或撤销进程时系统都要为之分配或回收PCB及其它资源。操作系统为此所付出的开销明显大于创建或撤销线程时的开销。类似地在进程切换时涉及进程上下文的切换而线程切换时只需保存和设置少量寄存器内容开销很小。此外由于同一进程内的多个线程共享进程的地址空间因此这些线程之间的同步与通信非常容易实现甚至无须操作系统的干预。支持多处理机系统对于传统单线程进程不管有多少处理机进程只能运行在一个处理机上。对于多线程进程可以将进程中的多个线程分配到多个处理机上执行。
线程状态
与进程一样各线程之间也存在共享资源和相互合作的制约关系致使线程在运行时也具有间断性。相应地线程在运行时也具有以下三种基本状态
运行状态线程已获得CPU而正在运行。就绪状态线程已具备各种执行条件只需再获得CPU便可立即执行。阻塞状态线程在执行中因某事件受阻而处于暂停状态。
线程这三种基本状态之间的转换和进程基本状态之间的转换是一样的。
线程控制
线程的创建操作系统中有用于创建线程和终止线程的函数或系统调用。用户程序启动时通常仅有一个称为初始化线程的线程正在执行其主要功能是用于创建新线程。在创建新线程时需要利用一个线程创建函数 并提供相应的参数。线程创建函数执行完后将返回一个线程标识符。线程的终止当一个线程完成自己的任务后或线程在运行中出现异常而要被强制终止时由终止线程调用相应的函数执行终止操作。但是有些线程主要是系统线程一旦被建立便一直运行而不会被终止。通常线程被终止后并不立即释放它所占有的资源只有当进程中的其它线程执行了分离函数后被终止线程才与资源分离此时的资源才能被其它线程利用。被终止但尚未释放资源的线程仍可被其它线程调用以使被终止线程重新恢复运行。
线程的实现方式
线程的实现可以分为两类用户级线程ULT和内核级线程KLT。
用户线程
在用户级线程中有关线程管理的所有工作都由应用程序在用户空间中完成内核意识不到线程的存在。应用程序可以通过使用线程库设计成多线程程序。通常应用程序从单线程开始在该线程中开始运行在其运行的任何时刻可以通过调用线程库中的派生函数创建一个在相同进程中运行的新线程。 对于设置了用户级线程的系统其调度仍是以进程为单位进行的各个进程轮流执行一个时间片。假设进程A包含1个用户级线程进程B包含100 个用户级线程这样进程A中线程的运行时间将是进程B中各线程运行时间的100倍。这种实现的优点如下
线程切换不需要转换到内核空间节省了模式切换的开销。调度算法可以是进程专用的不同的进程可根据自身的需要对自己的线程选择不同的调度算法。用户级线程的实现与操作系统平台无关对线程管理的代码是属于用户程序的一部分。
这种实现的缺点如下
系统调用的阻塞问题当线程执行一个系统调用时不仅该线程被阻塞而且进程内的所有线程都被阻塞。不能发挥多处理机的优势内核每次分配给一个进程的仅有一个CPU因此进程中仅有一个线程能执行。
内核线程
在操作系统中无论是系统进程还是用户进程都是在操作系统内核的支持下运行的与内核紧密相关。内核级线程同样也是在内核的支持下运行的线程管理的所有工作也是在内核空间内实现的。内核空间也为每个内核级线程设置一个TCB内核根据TCB感知某线程的存在并对其加以控制。 这种实现方式的优点如下
能发挥多处理机的优势内核能同时调度同一进程中的多个线程并行执行。如果进程中的一个线程被阻塞内核可以调度该进程中的其它线程占用处理机也可运行其它进程中的线程。内核支持线程具有很小的数据结构和堆栈线程切换比较快、开销小。内核本身也可采用多线程技术可以提高系统的执行速度和效率。
这种实现方式的缺点如下
同一进程中的线程切换需要从用户态转到核心态进行系统开销较大。这是因为用户进程的线程在用户态运行而线程调度和管理是在内核实现的。
混合方式
在组合实现方式中内核支持多个内核级线程的建立、调度和管理同时允许用户程序建立、调度和管理用户级线程。一些内核级线程对应多个用户级线程这是用户级线程通过时分多路复用内核级线程实现的。同一进程中的多个线程可以同时在多处理机上并行执行且在阻塞一个线程时不需要将整个进程阻塞所以组合方式能结合KLT和ULT的优点并且克服各自的不足。 实现线程库主要的方法有如下两种:.
在用户空间中提供一个没有内核支持的库。这种库的所有代码和数据结构都位于用户空间中。这意味着调用库内的一个函数只导致用户空间中的一个本地函数的调用。实现由操作系统直接支持的内核级的一个库。对于这种情况库内的代码和数据结构位于内核空间。调用库中的一个API函数通常会导致对内核的系统调用。
CPU调度
CPU调度是对CPU进行分配以实现进程并发地执行。CPU调度是多道程序操作系统的基础是操作系统设计的核心问题。
调度的层次
一个作业从提交开始直到完成往往要经历以下三级调度
高级调度作业调度按照一定的原则从外存上处于后备队列的作业中挑选一个或多个给它们分配必要的资源并建立相应的进程以使它们获得竞争CPU的权利。中级调度内存调度将那些暂时不能运行的进程调至外存等待此时进程的状态称为挂起态。当它们已具备运行条件且内存又稍有空闲时由中级调度来决定把外存上的那些己具备运行条件的就绪进程再重新调入内存并修改其状态为就绪态挂在就绪队列上等待。低级调度进程调度按照某种算法从就绪队列中选取一个进程将CPU分配给它。进程调度是最基本的一种调度在各种操作系统中都必须配置这级调度。进程调度的频率很高一般几十亳秒一 次。 调度的实现
调度器
在操作系统中用于调度和分派CPU的组件称为调度程序它通常由三部分组成
排队器将系统中的所有就绪进程按照一定的策略排成一个或多个队列以便于调度程序选择。每当有一个进程转变为就绪态时排队器便将它插入到相应的就绪队列中。分派器依据调度程序所选的进程将其从就绪队列中取出将CPU分配给新进程。上下文切换器在对CPU进行切换时会发生两对上下文的切换操作 第一对将当前进程的上下文保存到其PCB中再装入分派程序的上下文以便分派程序运行第二对移出分派程序的上下文将新选进程的CPU现场信息装入处理机的各个相应寄存器。 调度的时机、切换与过程
调度程序是操作系统内核程序。请求调度的事件发生后才可能运行调度程序调度了新的就绪进程后才会进行进程切换。理论上这三件事情应该顺序执行但在实际的操作系统内核程序运行中若某时刻发生了引起进程调度的因素则不一定能马上进行调度与切换。现代操作系统中不能进行进程的调度与切换的情况有以下几种
在处理中断的过程中中断处理过程复杂在实现上很难做到进程切换而且中断处理是系统工作的一部分逻辑上不属于某一进程不应被剥夺处理机资源。进程在操作系统内核临界区中进入临界区后需要独占式地访问理论上必须加锁以防止其它并行进程进入在解锁前不应切换到其它进程以加快临界区的释放。其它需要完全屏蔽中断的原子操作过程中如加锁、解锁、中断现场保护、恢复等原子操作。在原子过程中连中断都要屏蔽更不应该进行进程调度与切换。
若在上述过程中发生了引起调度的条件则不能马上进行调度和切换应置系统的请求调度标志直到上述过程结束后才进行相应的调度与切换。应该进行进程调度与切换的情况如下
发生引起调度条件且当前进程无法继续运行下去时可以马上进行调度与切换。若操作系统只在这种情况下进行进程调度则是非抢占调度。中断处理结束或自陷处理结束后返回被中断进程的用户态程序执行现场前若置上请求调度标志即可马上进行进程调度与切换。若操作系统支持这种情况下的运行调度程序则实现了抢占方式的调度。
进程切换往往在调度完成后立刻发生它要求保存原进程当前断点的现场信息恢复被调度进程的现场信息。现场切换时操作系统内核将原进程的现场信息推入当前进程的内核堆栈来保存它们并更新堆栈指针。内核完成从新进程的内核栈中装入新进程的现场信息、更新当前运行进程空间指针、重设PC寄存器等相关工作之后开始运行新的进程。
进程调度的方式
所谓进程调度方式是指当某个进程正在CPU上执行时若有某个更为重要或紧迫的进程需要处理即有优先权更高的进程进入就绪队列此时应如何分配CPU。通常有以下两种进程调度方式:
非抢占调度方式是指当一个进程正在CPU上执行时即使有某个更为重要或紧迫的进程进入就绪队列仍然让正在执行的进程继续执行直到该进程运行完成或发生某种事件而进入阻塞态时才把CPU分配给其他进程。非抢占调度方式的优点是实现简单、系统开销小适用于大多数的批处理系统但它不能用于分时系统和大多数的实时系统。抢占调度方式是指当一个进程正在处理机上执行时若有某个更为重要或紧迫的进程需要使用CPU则允许调度程序根据某种原则去暂停正在执行的进程将处理机分配给这个更为重要或紧迫的进程。抢占调度方式对提高系统吞吐率和响应效率都有明显的好处。但抢占不是一种任意性行为必须遵循一定的原则主要有优先权、短进程优先和时间片原则等。
闲逛进程
在进程切换时如果系统中没有就绪进程就会调度闲逛进程运行如果没有其它进程就绪该进程就一直运行 并在执行过程中测试中断。闲逛进程的优先级最低没有就绪进程时才会运行闲逛进程只要有进程就绪就会立即让出处理机。闲逛进程不需要CPU之外的资源它不会被阻塞。
两种线程的调度
用户级线程调度由于内核并不知道线程的存在所以内核还是和以前一样选择一个进程并给予时间控制。由进程中的调度程序决定哪个线程运行。内核级线程调度。内核选择一个特定线程运行通常不用考虑该线程属于哪个进程。对被选择的线程赋予一个时间片如果超过了时间片就会强制挂起该线程。
用户级线程的线程切换在同一进程中进行仅需少量的机器指令内核级线程的线程切换需要完整的上下文切换、修改内存映像、使高速缓存失效这就导致了若干数量级的延迟。
进程切换
对于通常的进程而言其创建、撤销及要求由系统设备完成的I/O操作都是利用系统调用而进入内核再由内核中的相应处理程序予以完成的。进程切换同样是在内核的支持下实现的因此可以说任何进程都是在操作系统内核的支持下运行的是与内核紧密相关的。
上下文切换切换CPU到另–个进程需要保存当前进程状态并恢复另一个进程的状态这个任务称为上下文切换。上下文是指某一时刻CPU寄存器和程序计数器的内容。进行上下文切换时内核会将旧进程状态保存在其PCB中然后加载经调度而要执行的新进程的上下文。上下文切换实质上是指处理机从一个进程的运行转到另一个进程上运行在这个过程中进程的运行环境产生了实质性的变化。上 下文切换的流程如下 挂起一个进程保存CPU上下文包括程序计数器和其他寄存器。更新PCB信息。把进程的PCB移入相应的队列如就绪、在某事件阻塞等队列。选择另一个进程执行并更新其PCB。跳转到新进程PCB中的程序计数器所指向的位置执行。恢复处理机上下文。 上下文切换的消耗上下文切换通常是计算密集型的即它需要相当可观的CPU时间在每秒几十上百次的切换中每次切换都需要纳秒量级的时间所以上下文切换对系统来说意味着消耗大量的CPU时间。有些处理器提供多个寄存器组这样上下文切换就只需要简单改变当前寄存器组的指针。上下文切换与模式切换模式切换与上下文切换是不同的模式切换时CPU逻辑上可能还在执行同一进程。用户进程最开始都运行在用户态若进程因中断或异常进入核心态运行执行完后又回到用户态刚被中断的进程运行。用户态和内核态之间的切换称为模式切换而不是上下文切换因为没有改变当前的进程。上下文切换只能发生在内核态它是多任务操作系统中的一个必需的特性。
同步与互斥
在多道程序环境下进程是并发执行的不同进程之间存在着不同的相互制约关系。为了协调进程之间的相互制约关系引入了进程同步的概念。
临界资源和临界区虽然多个进程可以共享系统中的各种资源但其中许多资源一次只能为一个进程所用我们将一次仅允许一个进程使用的资源称为临界资源。对临界资源的访问必须互斥地进行在每个进程中访问临界资源的那段代码称为临界区。为了保证临界资源的正确使用可把临界资源的访问过程分成4个部分 进入区为了进入临界区使用临界资源在进入区要检查可否进入临界区若能进入临界区则应设置正在访问临界区的标志以阻止其它进程同时进入临界区。临界区进程中访问临界资源的那段代码。退出区将正在访问临界区的标志清除。剩余区代码中的其余部分。 同步 是指为完成某种任务而建立的两个或多个进程这些进程因为需要在某些位置上协调它们的工作次序而等待、传递信息所产生的制约关系。进程间的直接制约关系源于它们之间的相互合作。互斥当一个进程进入临界区使用临界资源时另一个进程必须等待当占用临界资源的进程退出临界区后另一进程才允许去访问此临界资源。
实现临界区互斥的基本方法
软件实现方式在进入区设置并检查一些标志来标明是否有进程在临界区中若已有进程在临界区则在进入区通过循环检查进行等待进程离开临界区后则在退出区修改标志。
单标志法该算法可确保每次只允许一个进程进入临界区。但两个进程必须交替进入临界区若某个进程不再进入临界区则另一个进程也将无法进入临界区。
//P0进程 //P1进程
while(turn!0); while(turn!1); //进入区
critical section; critical section; //临界区
turn1; turn0; //退出去
remainder section; remainder section; //剩余区双标志先检查该算法的优点在于不用交替进入缺点在于两个进程可能同时进入临界区。
//Pi进程 //Pj进程
while(flag[j]); while(flag[i]); //进入区
flag[i]true; flag[j]true; //进入区
critical section; critical section; //临界区
flag[i]false; flag[j]false; //退出区
remainder section; remainder section; //剩余区双标志法后检查该算法优点在于两个进程不可能同时进入临界区缺点在于双方相互谦让从而导致饥饿现象。
//Pi进程 //Pj进程
flag[i]true; flag[j]true; //进入区
while(flag[j]); while(flag[i]); //进入区
critical section; critical section; //临界区
flag[i]false; flag[j]false; //退出区
remainder section; remainder section; //剩余区Peterson’s Algorithm该算法解决了饥饿现象。
//Pi进程 //Pj进程
flag[i]true;turnj; flag[j]true;turni; //进入区
while(flag[j]turnj); while(flag[i]turni); //进入区
critical section; critical section; //临界区
flag[i]false; flag[j]false; //退出区
remainder section; remainder section; //剩余区硬件实现方式计算机提供了特殊的硬件指令允许对一个字中的内容进行检测和修正或对两个字的内容进行交换等。通过硬件支持实现临界段问题的方法称为元方法。
中断屏蔽方法当一个进程正在执行它的临界区代码时防止其它进程进入其临界区的最简方法是关中断。因为CPU只在发生中断时引起进程切换因此屏蔽中断能够保证当前运行的进程让临界区代码顺利地执行完进而保证互斥的正确实现然后执行开中断。
//...
关中断;
临界区;
开中断;
//...硬件指令方法
TestAndSet指令这条指令是原子操作即执行改代码时不允许被中断。其功能是读出指定标志后把该标志设置为真。可以为每个临界资源设置一个共享布尔变量lock表示资源的两种状态true表示被占用初值为false。进程在进入临界资源之前利用TestAndSet指令检查标志lock若无进程在临界区则其值为false可以进入关闭临界资源把lock设置为true使任何进程都不能进入临界区若有进程在临界区则循环检查直到进程退出。
boolean TestAndSet(boolean * lock){boolean old;old*lock;*locktrue;return old;
}while TestAndSet(lock);
进程的临界区代码;
lockfalse;
进程的其它代码;Swap指令该指令的功能是交换两个字的内容。可以为每个临界资源设置一个共享布尔变量lock初值为false在每个进程中再设置一个局部布尔变量key用于与lock交换信息。在进入临界区前先利用Swap指令交换lock与key的内容然后检查key的状态有进程在临界区时重复交换和检查过程直到进程退出。
Swap(boolean *a,boolean *b){boolean temp;Temp*a;*a*b;*btemp;
}keytrue;
while(key!false){Swap(lock,key);
}
进程的临界区代码;
lockfalse;
进程的其它代码;互斥锁
互斥锁是解决临界区最简单的工具一个进程在进入临界区时应获得锁在退出临界区时释放锁。函数acquire()获得锁而函数release()释放锁。每个互斥锁有一个布尔变量available表示锁是否可用。如果锁是可用的调用acqiure()会成功且锁不再可用。当一个进程试图获取不可用的锁时会被阻塞直到锁被释放。acquire()或release()的执行必须是原子操作因此互斥锁通常采用硬件机制来实现。
acquire(){while(!available);availablefalse;
}
release(){availabletrue;
}管程
系统中的各种硬件资源和软件资源均可用数据结构抽象地描述其资源特性即用少量信息和对资源所执行的操作来表征该资源而忽略它们的内部结构和实现细节。利用共享数据结构抽象地表示系统中的共享资源而把对该数据结构实施的操作定义为一组过程。进程对共享资源的申请、释放等操作都通过这组过程来实现这组过程还可以根据资源情况或接受或阻塞进程的访问确保每次仅有-一个进程使用共享资源这样就可以统一管理对共享资源的所有访问实现进程互斥。这个代表共享资源的数据结构以及由对该共享数据结构实施操作的一组过程所组成的资源管理程序称为管程monitor。管程定义了一个数据结构和能为并发进程所执行在该数据结构上的一组操作这组操作能同步进程和改变管程中的数据。
monitor Demo{//定义一个名为Demo的管程//定义一个共享数据结构对应系统中的某种共享资源共享数据结构S//对共享数据结构初始化init_code(){...}//申请一个资源take_away(){对共享数据机构x的一系列处理}//归还一个资源give_back(){对共享数据机构x的一系列处理}
}当一个进程进入管程后被阻塞直到阻塞的原因解除时在此期间如果该进程不释放管程那么其它进程无法进入管程。为此将阻塞原因定义为条件变量condition。通常一个进程被阻塞的原因可以有多个因此在管程中设置了多个条件变量。每个条件变量保存了一个等待队列用于记录因该条件变量而阻塞的所有进程对条件变量只能进行两种操作即wait和signal。
x.wait当x对应的条件不满足时正在调用管程的进程调用x.wait将自己插入x条件的等待队列并释放管程。此时其它进程可以使用该管程。x.signal: x对应的条件发生了变化则调用x.signal,唤醒一个因x条件而阻塞的进程。
monitor Demo {共享数据结构s;condition X;//定义一个条件变量xinit_ code() { ...}take_away() {if(S0) {x.wait();//资源不够在条件变量x上阻塞等待}资源足够分配资源做一系列相应处理;}give_back(){归还资源做一系列相应处理;if (有进程在等待){x.signal; / /唤醒一个阻塞进程} }Java并发模型
在Java中当我们启动main()函数时其实就是启动了一个JVM 的进程而 main() 函数所在的线程就是这个进程中的一个线程也称主线程。Java线程如何实现并不受JVM规范的约束它与具体的虚拟机实现相关。以HotSpot为例它的每一个Java线程都是直接映射到一个操作系统原生线程来实现的。目前使用的两种主要线程库是POSIX Pthreads、Windows API。Pthreads作为POSIX标准的扩展可以提供用户级或内核级的库。Windows线程库是用于Windows操作系统的内核级线程库。Java线程API允许线程在Java程序中直接创建和管理。然而由于JVM实例通常运行在宿主操作系统之上Java 线程API通常采用宿主系统的线程库来实现因此在Windows系统中Java线程通常采Windows API来实现在类UNIX系统中采用Pthreads来实现。
线程组成
下图是一个Java进程运行时的JVMjdk8内存模型一个进程中可以有多个线程其中堆和元空间是线程共享的程序计数器、本地方法栈和堆是每个线程都有的。
线程状态
Java线程在生命周期内一定属于以下六种状态中的一个
NEW线程被创建但还没有调用start()方法。RUNNABLE调用start()方法之后该状态包含操作系统层面的就绪状态、运行状态和阻塞状态。BLOCKED当一个线程试图获取一个内部的对象锁但该锁被其它线程持有时该线程就会进人该状态。当锁被释放并且线程调度器允许该线程持有它的时候该线程将返回到原来的状态。WAITING当线程等待某个条件出现时它自己就进入该状态。TIMED_WAITING当线程等待某个条件出现并设置了最大的等待时间时它就进入了该状态。TERMINATED当run()方法执行结束或因其它原因终止时就进入该状态。 线程控制
线程创建
在Java中Thread类表示线程创建一个线程就是实例化一个Thread类对象。
Thread(Runnable target) //接收一个任务创建一个线程
Thread(Runnable target, String name) //接收一个任务和线程名字创建一个线程
void start() //启动线程
boolean isAlive() //判断线程是否还活着
static Thread currentThread() //获取当前线程
void setDaemon(boolean on) //将当前线程设置为守护线程
boolean isDaemon() //判断当前线程是否为守护线程
String getName() //获取线程的名字
void setName(String name) //设置线程的名字任务
线程在创建时必须为其指定任务Java中有以下两种任务
Runnable没有返回结果并且不会抛出异常。Callable有返回结果并且会抛出异常。
#mermaid-svg-juUNoQDj7P0gYZO4 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-juUNoQDj7P0gYZO4 .error-icon{fill:#552222;}#mermaid-svg-juUNoQDj7P0gYZO4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-juUNoQDj7P0gYZO4 .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-juUNoQDj7P0gYZO4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-juUNoQDj7P0gYZO4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-juUNoQDj7P0gYZO4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-juUNoQDj7P0gYZO4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-juUNoQDj7P0gYZO4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-juUNoQDj7P0gYZO4 .marker.cross{stroke:#333333;}#mermaid-svg-juUNoQDj7P0gYZO4 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-juUNoQDj7P0gYZO4 g.classGroup text{fill:#9370DB;fill:#131300;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-juUNoQDj7P0gYZO4 g.classGroup text .title{font-weight:bolder;}#mermaid-svg-juUNoQDj7P0gYZO4 .nodeLabel,#mermaid-svg-juUNoQDj7P0gYZO4 .edgeLabel{color:#131300;}#mermaid-svg-juUNoQDj7P0gYZO4 .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-juUNoQDj7P0gYZO4 .label text{fill:#131300;}#mermaid-svg-juUNoQDj7P0gYZO4 .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-juUNoQDj7P0gYZO4 .classTitle{font-weight:bolder;}#mermaid-svg-juUNoQDj7P0gYZO4 .node rect,#mermaid-svg-juUNoQDj7P0gYZO4 .node circle,#mermaid-svg-juUNoQDj7P0gYZO4 .node ellipse,#mermaid-svg-juUNoQDj7P0gYZO4 .node polygon,#mermaid-svg-juUNoQDj7P0gYZO4 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-juUNoQDj7P0gYZO4 .divider{stroke:#9370DB;stroke:1;}#mermaid-svg-juUNoQDj7P0gYZO4 g.clickable{cursor:pointer;}#mermaid-svg-juUNoQDj7P0gYZO4 g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-juUNoQDj7P0gYZO4 g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-juUNoQDj7P0gYZO4 .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-juUNoQDj7P0gYZO4 .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-juUNoQDj7P0gYZO4 .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-juUNoQDj7P0gYZO4 .dashed-line{stroke-dasharray:3;}#mermaid-svg-juUNoQDj7P0gYZO4 #compositionStart,#mermaid-svg-juUNoQDj7P0gYZO4 .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-juUNoQDj7P0gYZO4 #compositionEnd,#mermaid-svg-juUNoQDj7P0gYZO4 .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-juUNoQDj7P0gYZO4 #dependencyStart,#mermaid-svg-juUNoQDj7P0gYZO4 .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-juUNoQDj7P0gYZO4 #dependencyStart,#mermaid-svg-juUNoQDj7P0gYZO4 .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-juUNoQDj7P0gYZO4 #extensionStart,#mermaid-svg-juUNoQDj7P0gYZO4 .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-juUNoQDj7P0gYZO4 #extensionEnd,#mermaid-svg-juUNoQDj7P0gYZO4 .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-juUNoQDj7P0gYZO4 #aggregationStart,#mermaid-svg-juUNoQDj7P0gYZO4 .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-juUNoQDj7P0gYZO4 #aggregationEnd,#mermaid-svg-juUNoQDj7P0gYZO4 .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-juUNoQDj7P0gYZO4 .edgeTerminals{font-size:11px;}#mermaid-svg-juUNoQDj7P0gYZO4 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}Futureboolean cancel(boolean mayInterruptIfRunning)V get()V get(long timeout, TimeUnit unit)boolean isCancelled()boolean isDone()RunnableFutureScheduledFutureRunnablevoid run()RunnableScheduledFuture«class»FutureTaskFutureTask(Callable callable)FutureTask(Runnable runnable, V result)CallableVV call()FutureTask将这两种任务包装起来。一方面它实现了Runnable接口可以被线程执行另一方面它实现了Future接口可以表示异步计算的结果。Future提供的方法可以用于检查任务是否完成、等待任务完成和检索任务结果。例如get()方法可以阻塞获取结果cancel()方法可以取消任务。但是一旦任务开始就不会被其它线程重复执行一旦任务完成就不能被取消。
线程池
线程池改变了手动创建并管理线程的现状它将任务和线程解耦通过复用已有线程来执行任务极大提高了程序性能。
#mermaid-svg-ePu9sgtvNMwJtgUr {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-ePu9sgtvNMwJtgUr .error-icon{fill:#552222;}#mermaid-svg-ePu9sgtvNMwJtgUr .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ePu9sgtvNMwJtgUr .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-ePu9sgtvNMwJtgUr .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ePu9sgtvNMwJtgUr .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ePu9sgtvNMwJtgUr .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ePu9sgtvNMwJtgUr .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ePu9sgtvNMwJtgUr .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ePu9sgtvNMwJtgUr .marker.cross{stroke:#333333;}#mermaid-svg-ePu9sgtvNMwJtgUr svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ePu9sgtvNMwJtgUr g.classGroup text{fill:#9370DB;fill:#131300;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-ePu9sgtvNMwJtgUr g.classGroup text .title{font-weight:bolder;}#mermaid-svg-ePu9sgtvNMwJtgUr .nodeLabel,#mermaid-svg-ePu9sgtvNMwJtgUr .edgeLabel{color:#131300;}#mermaid-svg-ePu9sgtvNMwJtgUr .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-ePu9sgtvNMwJtgUr .label text{fill:#131300;}#mermaid-svg-ePu9sgtvNMwJtgUr .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-ePu9sgtvNMwJtgUr .classTitle{font-weight:bolder;}#mermaid-svg-ePu9sgtvNMwJtgUr .node rect,#mermaid-svg-ePu9sgtvNMwJtgUr .node circle,#mermaid-svg-ePu9sgtvNMwJtgUr .node ellipse,#mermaid-svg-ePu9sgtvNMwJtgUr .node polygon,#mermaid-svg-ePu9sgtvNMwJtgUr .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ePu9sgtvNMwJtgUr .divider{stroke:#9370DB;stroke:1;}#mermaid-svg-ePu9sgtvNMwJtgUr g.clickable{cursor:pointer;}#mermaid-svg-ePu9sgtvNMwJtgUr g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-ePu9sgtvNMwJtgUr g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-ePu9sgtvNMwJtgUr .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-ePu9sgtvNMwJtgUr .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-ePu9sgtvNMwJtgUr .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-ePu9sgtvNMwJtgUr .dashed-line{stroke-dasharray:3;}#mermaid-svg-ePu9sgtvNMwJtgUr #compositionStart,#mermaid-svg-ePu9sgtvNMwJtgUr .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ePu9sgtvNMwJtgUr #compositionEnd,#mermaid-svg-ePu9sgtvNMwJtgUr .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ePu9sgtvNMwJtgUr #dependencyStart,#mermaid-svg-ePu9sgtvNMwJtgUr .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ePu9sgtvNMwJtgUr #dependencyStart,#mermaid-svg-ePu9sgtvNMwJtgUr .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ePu9sgtvNMwJtgUr #extensionStart,#mermaid-svg-ePu9sgtvNMwJtgUr .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ePu9sgtvNMwJtgUr #extensionEnd,#mermaid-svg-ePu9sgtvNMwJtgUr .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ePu9sgtvNMwJtgUr #aggregationStart,#mermaid-svg-ePu9sgtvNMwJtgUr .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ePu9sgtvNMwJtgUr #aggregationEnd,#mermaid-svg-ePu9sgtvNMwJtgUr .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-ePu9sgtvNMwJtgUr .edgeTerminals{font-size:11px;}#mermaid-svg-ePu9sgtvNMwJtgUr :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}ExecutorExectorServiceScheduledExecutorService«class»ThreadPoolExecutor«class»ScheduledThreadPoolExecutorJava线程池由Executor框架实现Executor意为执行器用于启动线程通过Executor来启动线程比使用 Thread 的 start()方法更好除了更易管理效率更好外还有助于避免 this 逃逸问题。
void execute(Runnable command) ExecutorService是一种提供了关闭和产生Future方法的执行器。
void shutdown()//有序关闭执行器在此过程中执行之前提交的任务但不接受新任务。
ListRunnable shutdownNow()//尝试停止所有正在执行的任务停止等待任务的处理并返回等待执行的任务列表。
boolean awaitTermination(long timeout, TimeUnit unit)
boolean isShutdown()//如果执行器已关闭则返回true。
boolean isTerminated()//如果所有任务在执行器关闭后都已完成则返回true。
T FutureT submit(CallableT task)
Future? submit(Runnable task)
T FutureT submit(Runnable task, T result)
//执行给定的任务如果有成功完成的任务则返回成功完成的任务的结果。
T T invokeAny(Collection? extends CallableT tasks)
T T invokeAny(Collection? extends CallableT tasks, long timeout, TimeUnit unit)
//执行给定的任务在所有任务完成时返回一个Future列表其中包含它们的状态和结果。
T ListFutureT invokeAll(Collection? extends CallableT tasks)
T ListFutureT invokeAll(Collection? extends CallableT tasks, long timeout, TimeUnit unit) ScheduledExecutorService是一种可以延迟或定期执行任务的执行器。
//在delay时间后执行任务
ScheduledFuture? schedule(Runnable command, long delay, TimeUnit unit)
V ScheduledFutureV schedule(CallableV callable, long delay, TimeUnit unit)
//在initialDelay时间后执行任务,在period时间间隔内重复执行
ScheduledFuture? scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
//在initialDelay时间后执行任务,在上个任务执行完成后延时delay继续执行下一个任务
ScheduledFuture? scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)线程池的构造
所有线程池都是ThreadPoolExecutor的实例通过ThreadPoolExecutor的构造方法可以了解线程池的构造并自定义线程池。
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueueRunnable workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)maximumPoolSize和corePoolSizecorePoolSize表示核心线程的数量maximumPoolSize表示最大线程数量。ThreadPoolExecutor可以根据和两个参数设置的边界自动调节线程池的大小。当在方法execute(Runnable)中提交了一个新任务并且运行的线程少于corePoolSize则创建一个新线程来处理请求即使其它工作线程处于空闲状态。如果运行的线程数大于corePoolSize但小于maximumPoolSize则只有在队列已满时才会创建新线程。keepAliveTime如果线程池当前有超过corePoolSize的线程并且多余的线程的空闲时间超过keepAliveTime时将被终止。threadFactory如果没有特别指定则使用Executors.defaultThreadFactory()。它创建线程池中的所有线程使其位于同一个ThreadGroup中具有相同的NORM_PRIORITY优先级和非守护进程状态。workQueue用于保存未被执行任务的工作队列。handler当线程池中的workQueue已满并且线程数量已到达maximumPoolSize时线程池就需要调用handler.rejectedExecution(Runnable r, ThreadPoolExecutor executor)方法拒绝新提交的任务。线程池预定义了以下四个拒绝策略 AbortPolicy直接抛出异常。CallerRunsPolicy使用调用execute方法的线程执行任务。DiscardOldestPolicy丢弃workQueue中最老的任务将新任务添加到workQueue中。DiscardPolicy丢弃无法处理的任务。
工作流程
当调用execute方法提交任务时线程池会经过以下步骤
如果运行的线程少于corePoolSize线程池会创建一个新线程执行任务。如果corePoolSize或更多线程正在运行线程池会将任务添加到workQueue中。workQueue已满并且线程数量未到达maximumPoolSize继续创建线程执行任务。线程数已达maximumPoolSize执行拒绝策略。 钩子方法
线程池提供了以下两个钩子方法可以在每个任务执行之前和之后调用它们。如果钩子方法抛出异常内部工作线程可能依次失败并突然终止。
protected void beforeExecute(Thread t, Runnable r)
protected void afterExecute(Runnable r, Throwable t)队列的维护
方法getQueue允许访问工作队列以便进行监视和调试。remove和purge方法可用于在大量排队任务被取消时协助进行存储回收。
BlockingQueueRunnable getQueue()
void purge()//尝试从工作队列中删除已取消的所有Future任务。
boolean remove(Runnable task)终结
程序中不再引用且没有剩余线程的池将自动关闭。但是核心线程一旦创建就不会自动死亡如果希望即使忘记调用shutdown也能回收未使用的线程池那么必须安排未使用的线程最终死亡方法是使用零个核心线程或设置核心线程的空闲时间。
void allowCoreThreadTimeOut(boolean value)//设置控制核心线程是否会超时并在保持活动时间内没有任务到达时终止的策略如果新任务到达时需要替换核心线程。当为false时核心线程永远不会因为缺少传入的任务而终止。当为true时应用于非核心线程的保持活动策略也应用于核心线程。预定义线程池
Executors工具类提供了创建预定义线程池的方法
static ExecutorService newCachedThreadPool()
static ExecutorService newFixedThreadPool(int nThreads)
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
static ExecutorService newSingleThreadExecutor()
static ScheduledExecutorService newSingleThreadScheduledExecutor()
static ExecutorService newWorkStealingPool()但最好还是使用构造函数创建线程池因为这种方式会让线程池的使用者更好的了解线程池的构造并且会规避资源耗尽的风险。
线程中断
线程中断是指给某一线程发送一个请求中断信号而停不停止运行完全取决于被请求线程。在Java中可以通过调用interrupt()方法对某一线程发送中断请求当此方法被调用时被请求线程的中断标志将被设置为true这是每一个线程都具有的boolean标志在每个线程内都应该不时地检测这个标志以判断线程是否被请求中断并决定是否接受请求。
void interrupt() //向线程发送中断请求,并将中断标志设置为true
boolean isInterrupted() //判断是否被请求中断
static boolean interrupted() //判断是否被请求中断并清除标志位线程阻塞
线程阻塞是指让当前线程进入TIMED_WAATING或WAITING状态当线程进入阻塞状态时无法响应外部中断请求那么导致线程阻塞的方式必须解决这一问题据此可以将阻塞方式分为以下两种
抛出异常式顾名思义抛出异常的方式在当前线程被请求中断时会抛出一个InterruptedException异常。在抛出异常后会将中断标志清空。此类方式如下
//Thread
static void sleep(long millis)
void join() //阻塞当前线程至调用线程结束
void join(long millis)
//TimeUnit
void sleep(long timeout)不抛出异常式LockSupport就是不抛出异常方式的实现它可以在线程内任意位置让线程阻塞并且不需要先获取某个对象的锁也不会抛出InteruptedException异常。 LockSupport使用了一种名为Permit的许可概念来做到阻塞和唤醒线程每个线程都只有一个许可如果许可可用则park方法就会立即返回并在过程中消耗它否则就会阻塞也可以使用unpark方法获取许可。外部请求中断时park方法会立即返回并且不会清空中断标志。
static Object getBlocker(Thread t)//返回提供给最近调用的尚未解除阻塞的park方法的阻塞对象如果未被阻塞则返回null。
static void park() //阻塞当前线程
static void parkNanos(long nanos) //阻塞指定时间
static void parkUntil(long deadline) //最多阻塞指定时间
static void unpark(Thread thread) //解除阻塞参数线程线程安全
JMM
JMMJava内存模型类似于操作系统内存模型它有以下两个作用
抽象了主内存所有线程创建的对象都必须放在其中和工作内存JMM抽象出来的一个概念每个线程都有一个工作内存并且线程只能访问自己的工作内存之间的关系。当线程需要访问共享变量时必须将共享变量加载到工作内存并保存为一个副本且线程对共享变量的操作都只能对工作内存中的副本进行。当多个线程同时操作一个共享变量临界资源时就会引发线程安全问题。规定了从 Java 源代码到 CPU 可执行指令的这个转化过程要遵守哪些和并发相关的原则和规范。为了提升速度和性能计算机在执行指令时会对指令进行重排序即计算机在执行代码的时候并不一定是按照我们所写的代码的顺序依次执行。Java 源代码会经历编译器优化重排→指令并行重排→内存系统重排的过程最终才变成操作系统可执行的指令序列。指令重排序可以保证串行语义一致但是没有义务保证多线程间的语义也一致 所以在多线程下指令重排序可能会导致线程安全问题。 解决线程安全的过程就是实现以下三个性质的过程
内存可见性指某个线程修改了共享变量的值新值对其它线程来说是立即可见的。有序性指Java源代码不会被重排序。原子性指某个线程对共享变量的操作一旦开始就不会被其它线程干扰。
其中实现原子性一定可以保证线程安全而实现内存可见性和有序性只在特定情况下才能保证线程安全。相应的以后者实现的线程安全代价也自然低。
不可变对象
如果一个共享变量在初始化之后就不能被改变那么这种对象就称为不可变对象不可变对象一定是线程安全的。不可变对象的实现方式如下
如果共享变量是一个基本类型那么只要在声明时使用final修饰就可以保证该共享变量是不可变的。如果一个共享变量是对象类型那么对象自身要保证其行为不会对其状态产生任何影响。例如String的实现。
ThreadLocal
如果一个变量是线程独有的、不可共享的那么这个变量就一定是线程安全的这种对象称之为非共享对象。通过ThreadLocal就可以实现非共享对象。每一个Thread对象内均含有一个ThreadLocalMap类型的成员变量threadLocalsThreadLocalMap定义在ThreadLocal中它存储了以ThreadLocal为键、Object为值的键值对threadLocals变量的作用是存储线程的非共享对象。
ThreadLocal.ThreadLocalMap threadLocals null;ThreadLocal的使用流程是这样的首先通过ThreadLocal将共享变量包装
private static ThreadLocalSharedVariablethreadLocalnew ThreadLocalSharedVariable();然后在每个线程中使用ThreadLocal提供的get和set方法设置共享变量的值。在这个过程中ThreadLocal充当当前线程中ThreadLocalMap的访问入口。set方法的执行过程如下
public void set(T value) {//获取当前线程Thread t Thread.currentThread();//获取当前线程的ThreadLocalMapThreadLocalMap map getMap(t);if (map ! null)//以自身为键共享变量值为值存放到ThreadLocalMap中map.set(this, value);elsecreateMap(t, value);
}不难看出ThreadLocal通过在每个线程的ThreadLocalMap中保存当前线程设置的共享变量值的方式来实现非共享对象。ThreadLocalMap通过一个Entry来保存存入的键和值Entry的定义如下 static class Entry extends WeakReferenceThreadLocal? {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal? k, Object v) {super(k);value v;}}其中将键实现为弱引用值实现为强引用当键被GC自动回收而值不会被回收时就会造成内存泄漏问题。ThreadLocalMap 实现中已经考虑了这种情况在调用 set、get、remove方法的时候会清理掉 key 为 null 的记录。因此在使用完ThreadLocal方法后最好手动调用remove方法。
Unsafe类解析
Unsafe类是一个提供底层、不安全本地方法的工具类它通过单例模式实现并且只能由启动类加载器加载的类才能获取它的实例在JUC包下大量的使用了它。
public final class Unsafe {...private Unsafe() {}private static final Unsafe theUnsafe new Unsafe();public static Unsafe getUnsafe() {Class? caller Reflection.getCallerClass();//判断调用此方法的类是否由启动类加载器加载如果不是直接抛出异常if (!VM.isSystemDomainLoader(caller.getClassLoader()))throw new SecurityException(Unsafe);return theUnsafe;}...
}如果我们想使用这个类可以使用反射的方式获取它的实例
Field theUnsafe Unsafe.class.getDeclaredField(theUnsafe);
theUnsafe.setAccessible(true);
Unsafe unsafe (Unsafe) theUnsafe.get(null);它提供的功能如下
内存操作直接对内存进行操作以这种方式操作的内存属于堆外内存在垃圾回收时不会自动回收需要手动进行释放。
public native long allocateMemory(long bytes);
public native long reallocateMemory(long address, long bytes);
public native void setMemory(Object o, long offset, long bytes, byte value);
public native void copyMemory(Object srcBase, long srcOffset,Object destBase, long destOffset,long bytes);
public native void freeMemory(long address);内存屏障
public native void loadFence();
public native void storeFence();
public native void fullFence();对象操作
public native Object getObject(Object var1, long var2);
public native void putObject(Object var1, long var2, Object var4);public native void putOrderedObject(Object var1, long var2, Object var4);
public native void putOrderedInt(Object var1, long var2, int var4);
public native void putOrderedLong(Object var1, long var2, long var4);public native Object getObjectVolatile(Object var1, long var2);
public native void putObjectVolatile(Object var1, long var2, Object var4);public native long objectFieldOffset(Field var1);public native int arrayBaseOffset(Class? var1);
public native int arrayIndexScale(Class? var1);数据操作
public native long getAddress(long var1);
public native void putAddress(long var1, long var3);
CAS操作
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);线程调度
public native void unpark(Object var1);
public native void park(boolean var1, long var2);Class操作
public native long staticFieldOffset(Field var1);
public native Object staticFieldBase(Field var1);
public native boolean shouldBeInitialized(Class? var1);
public native void ensureClassInitialized(Class? var1);
public native Class? defineClass(String var1, byte[] var2, int var3, int var4, ClassLoader var5, ProtectionDomain var6);
public native Class? defineAnonymousClass(Class? var1, byte[] var2, Object[] var3);
public native Object allocateInstance(Class? var1);系统信息
public native int addressSize();
public native int pageSize();volatile
volatile关键字可以保证共享变量的内存可见性当一个共享变量被volatile关键字修饰时实际就是告诉JVM这个共享变量是不稳定的在使用时必须到主存中读取。volatile关键字也可以实现顺序性当读写一个被volatile关键字修饰的共享变量时volatile关键字会禁止指令重排序。volatile底层通过插入内存屏障的方式实现
写屏障写屏障保证在该屏障之前对共享变量的改动都会同步到主存当中并且不会进行指令重排。读屏障读屏障保证在该屏障之后对共享状态的读取都是主存中的最新数据并且不会进行指令重排。
若想只通过volatile关键字保证线程安全必须同时满足以下条件
不能是一个组合的临界变量。运算结果并不依赖共享变量的当前值或者只能由一条线程修改共享变量的值。
volatile关键字一个典型的应用就是单例模式2处实例化对象的过程分为多步分配内存空间、初始化、将对象地址赋值给INSTANCE引用变量如果没有使用volatile关键字修饰INSTANCE的话那么赋值步骤可能会在初始化步骤之前此时如果另一个线程在1处访问一个未初始化的对象就会产生异常。
public class Singleton {volatile private static Singleton INSTANCEnull;public static Singleton getInstance(){if (INSTANCEnull){//1synchronized (Singleton.class){if (INSTANCEnull){INSTANCEnew Singleton();//2}}}return INSTANCE;}
}原子类
原子类是一些通过CAS保证原子性操作特征的类。CAS是Compare And Swap比较并交换的缩写在CAS中有三个值
Var要更新的值Expected预期的值New新值
一次CAS操作的流程如下判断V是否等于E如果是则将V修改为N否则就自旋重复这个过程。Java中的CAS由Unsafe类中的本地方法实现。这些本地方法原子性由操作系统或CPU实现。如果从乐观锁和悲观锁的角度对Java中的锁进行分类那么对象锁和AQS都是悲观锁因为它们在访问临界资源时都会先加锁只有获得锁的线程才能访问临济资源。而CAS是乐观锁任何线程在访问临界资源的时都不需要加锁并且只有在满足条件的时候才能修改临界资源如果不满足条件就会一直尝试直到满足条件为止。但是CAS自身还存在着一些问题
ABA问题线程一将共享状态A改成了B然后又改成了A那么线程二将不会知道线程一的第一次修改。这就是ABA问题。
JUC包下的原子类如下所示
原子整数 AtomicInteger整型原子类AtomicLong长整型原子类AtomicBoolean布尔型原子类 原子引用 AtomicReference引用类型原子类AtomicStampedReference原子更新带有版本号的引用类型。该类将整数值与引用关联起来可用于解决原子的更新数据和数据的版本号可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。AtomicMarkableReference 原子更新带有标记位的引用类型 原子数组 AtomicIntegerArray整型数组原子类AtomicLongArray长整型数组原子类AtomicReferenceArray引用类型数组原子类 原子更新器一个基于反射支持对指定类的指定字段进行原子更新的类。使用原子更新器更新的字段必须是可访问的、必须被volatile修饰、不能被static修饰。 AtomicIntegerFieldUpdater原子更新整型字段的更新器AtomicLongFieldUpdater原子更新长整型字段的更新器AtomicReferenceFieldUpdater原子更新引用类型字段的更新器 原子累加器 DoubleAdderLongAdderDoubleAccumulatorLongAccumulator
对象锁
Java中每个对象都有一个对象锁每个对象的对象锁最多只能由同一个线程获得一次或多次。通过synchronized关键字即可以使用对象锁。synchronized的使用有以下三种形式
synchronized修饰实例方法进入同步代码块的线程会获得当前对象的对象锁。
synchronized public void method(){//synchronizedCode
}synchronized修饰静态方法进入同步代码块的线程会获得方法所在类的Class对象的对象锁。
synchronized static public void method(){//synchronizedCode
}synchronized修饰代码块进入同步代码块的线程会获得synchronized关键字指定对象的对象锁。
public void method(){//...synchronized (/*AnyObject*/){//synchronizedCode}//...
}对象锁的实现
在JDK6之前对象锁通过操作系统层面的管程实现在JDK6之后Java从JVM层面对对象锁进行了优化通过锁记录实现。
管程实现方式
以管程实现的对象锁成为重量级锁管程的逻辑结构如下图所示当一个线程获得对象锁时就会成为管程的Owner其它尝试获取锁的线程会进入管程的EntryList。当Owner为空时会唤醒EntryList中的线程被唤醒的线程通过调度重新获取对象锁。 锁记录实现方式
对象的对象头以HotSpot为例中有一片区域叫做Mark Word用于存储对象运行时的一些状态它的结构如下 JDK6以后对象锁分为以下几个状态级别由低到高
无锁状态偏向锁状态轻量级锁状态重量级锁状态
在获取对象锁的时候会伴随着一个锁升级的过程这个过程是单向的对象锁的每一个状态都会在Mark Word相应的标志位内体现。在优化后获取对象锁的流程如下 当只有一个线程获取对象锁时JVM会把Mark Word中的lock标志位设置为01把biased_lock标志位设置为1表示进入偏向模式同时使用CAS操作把获取到这个锁的线程ID记录到Mark Word中如果CAS操作成功持有偏向锁的线程以后每次进入这个锁相关的代码块时JVM都不用再进行任何同步操作。如果操作失败则需要进行锁升级。 之后一旦发现其它线程尝试获取该对象锁偏向模式会立即失败失败后标志位恢复到可偏向可重新获取偏向锁或未锁定状态。如果是未锁定状态JVM首先在当前线程内创建一个锁记录对象用于存储对象Mark Word的拷贝 然后JVM使用CAS操作将对象的Mark Word替换为指向锁记录的指针。如果CAS操作成功了该线程就获得了该对象的轻量级锁。如果CAS操作失败了那就意味着至少存在一条线程与当前线程竞争该对象的对象锁JVM首先会检查对象的Mark Word是否指向当前线程的锁记录如果是说明当前线程已经拥有了这个对象的锁否则就说明这个对象的对象锁已经被其它线程抢占了那么当前线程就会尝试自旋来获取锁当自旋次数达到临界值时轻量级锁就不再有效必须进行锁膨胀将轻量级锁变为重量级锁。当释放轻量级锁时如果对象的对象头仍指向当前线程的锁记录那就使用CAS操作将对象的指向执行锁记录的指针用锁记录中的Mark Word替换。如果替换失败了说明对象锁的状态重量级锁此时就进入重量级锁的释放过程。
线程通信
通过以下方法进行线程通信的前提是获取对象的对象锁此时的对象锁一定处于重量级锁状态当一个线程成为管程的Owner时发现某些运行条件不满足此时可以使用wait方法使线程进入WaitSet进入WaitSet的线程会在Owner线程调用notify方法时被唤醒唤醒后的线程进入EntryList进行锁抢夺。
void wait() //加入锁对象的等待序列
void wait(long timeout) //计时等待
void notify() //随机挑选一个等待线程激活
void notifyAll() //激活所有的等待线程值得注意的是调用wait方法的线程在哪里等待就会在哪里被唤醒所以下面代码中的if语句应换成while以免线程被唤醒时跳过if语句而产生虚假唤醒现象。
synchronized (obj){if (condition){obj.wait()}
}AQS
AQS是一个用于构建锁或其它同步组件的重量级框架。
#mermaid-svg-mbNcm0TvfBxvBD3X {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-mbNcm0TvfBxvBD3X .error-icon{fill:#552222;}#mermaid-svg-mbNcm0TvfBxvBD3X .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-mbNcm0TvfBxvBD3X .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-mbNcm0TvfBxvBD3X .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-mbNcm0TvfBxvBD3X .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-mbNcm0TvfBxvBD3X .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-mbNcm0TvfBxvBD3X .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-mbNcm0TvfBxvBD3X .marker{fill:#333333;stroke:#333333;}#mermaid-svg-mbNcm0TvfBxvBD3X .marker.cross{stroke:#333333;}#mermaid-svg-mbNcm0TvfBxvBD3X svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-mbNcm0TvfBxvBD3X g.classGroup text{fill:#9370DB;fill:#131300;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-mbNcm0TvfBxvBD3X g.classGroup text .title{font-weight:bolder;}#mermaid-svg-mbNcm0TvfBxvBD3X .nodeLabel,#mermaid-svg-mbNcm0TvfBxvBD3X .edgeLabel{color:#131300;}#mermaid-svg-mbNcm0TvfBxvBD3X .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-mbNcm0TvfBxvBD3X .label text{fill:#131300;}#mermaid-svg-mbNcm0TvfBxvBD3X .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-mbNcm0TvfBxvBD3X .classTitle{font-weight:bolder;}#mermaid-svg-mbNcm0TvfBxvBD3X .node rect,#mermaid-svg-mbNcm0TvfBxvBD3X .node circle,#mermaid-svg-mbNcm0TvfBxvBD3X .node ellipse,#mermaid-svg-mbNcm0TvfBxvBD3X .node polygon,#mermaid-svg-mbNcm0TvfBxvBD3X .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-mbNcm0TvfBxvBD3X .divider{stroke:#9370DB;stroke:1;}#mermaid-svg-mbNcm0TvfBxvBD3X g.clickable{cursor:pointer;}#mermaid-svg-mbNcm0TvfBxvBD3X g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-mbNcm0TvfBxvBD3X g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-mbNcm0TvfBxvBD3X .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-mbNcm0TvfBxvBD3X .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-mbNcm0TvfBxvBD3X .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-mbNcm0TvfBxvBD3X .dashed-line{stroke-dasharray:3;}#mermaid-svg-mbNcm0TvfBxvBD3X #compositionStart,#mermaid-svg-mbNcm0TvfBxvBD3X .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-mbNcm0TvfBxvBD3X #compositionEnd,#mermaid-svg-mbNcm0TvfBxvBD3X .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-mbNcm0TvfBxvBD3X #dependencyStart,#mermaid-svg-mbNcm0TvfBxvBD3X .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-mbNcm0TvfBxvBD3X #dependencyStart,#mermaid-svg-mbNcm0TvfBxvBD3X .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-mbNcm0TvfBxvBD3X #extensionStart,#mermaid-svg-mbNcm0TvfBxvBD3X .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-mbNcm0TvfBxvBD3X #extensionEnd,#mermaid-svg-mbNcm0TvfBxvBD3X .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-mbNcm0TvfBxvBD3X #aggregationStart,#mermaid-svg-mbNcm0TvfBxvBD3X .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-mbNcm0TvfBxvBD3X #aggregationEnd,#mermaid-svg-mbNcm0TvfBxvBD3X .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-mbNcm0TvfBxvBD3X .edgeTerminals{font-size:11px;}#mermaid-svg-mbNcm0TvfBxvBD3X :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}AbstractOwnableSynchronizerAbstractQueuedSynchronizerAbstractQueuedLongSynchronizer在具体的实现上它维护了一个代表临界资源状态的变量state和一个用于存放被阻塞线程的等待队列CLH锁队列的变体。
private volatile int state;
private transient volatile Node head;
private transient volatile Node tail;在临界资源的访问上它提供了以下两种方式
独占式同一时间只允许一个线程访问临界资源。共享式允许多个线程访问临界资源。
状态变量
状态变量在不同的临界资源访问方式上有不同的含义
独占式表示临界资源的锁定状态0表示未锁定1表示已锁定。共享式表示可以访问临界资源线程的个数。
AQS提供以下方法操作状态变量
protected final int getState()
protected final void setState(int newState)
protected final boolean compareAndSetState(int expect, int update)等待队列
等待队列由一个带有哨兵结点的双向链表实现链表结点是对等待线程的封装它包含线程本身以及结点的状态它的实现如下
static final class Node {//标记表示节点正在共享模式中等待static final Node SHARED new Node();//标记表示节点正在独占模式下等待static final Node EXCLUSIVE null;//等待状态volatile int waitStatus;static final int CANCELLED 1;static final int SIGNAL -1;static final int CONDITION -2;static final int PROPAGATE -3;//等待线程volatile Thread thread;//前结点volatile Node prev;//后结点volatile Node next;//一个正常排队的后继节点Node nextWaiter;...
}waitStatus变量表示结点的等待状态
0新结点入队时的状态。CANCELLED1表示当前结点已取消排队超时或被中断会触发变更为此状态进入该状态后的结点的状态将不会再变化。SIGNAL-1表示后继结点在等待当前结点唤醒。后继结点入队时会将前继结点的状态更新为SIGNAL。CONDITION-2表示结点等待在Condition上当其它线程调用了Condition的signal()方法后CONDITION状态的结点将从等待队列转移到同步队列中等待获取同步锁。PROPAGATE-3共享模式下前驱结点不仅会唤醒其后继结点同时也可能会唤醒后继的后继结点。
等待队列的核心功能就是调度处于不同状态的结点可以归结为以下几点
将等待获取临界资源的线程加入队列进行排队。清除在队列内但不参与排队的节点。让在队列内并且参与排队的节点尝试获取临界资源或阻塞。在满足条件下唤醒在队列内并且参与排队并且处于阻塞状态的节点。保存处在排队节点的中断标志排队结束后再响应中断。
AQS核心方法分析
//将当前线程根据指定模式加入等待队列
private Node addWaiter(Node mode) {//构造结点Node node new Node(Thread.currentThread(), mode);//将node加入队尾...return node;
}//排队过程中尝试获取临界资源成功获取临界资源后返回排队过程中是否被请求中断
final boolean acquireQueued(final Node node, int arg) {//是否成功获取资源boolean failed true;try {//自旋过程中是否被中断过boolean interrupted false;for (;;) {//前驱结点final Node p node.predecessor();//如果前驱节点是头节点说明自己有资格获取锁并且成功获取锁if (p head tryAcquire(arg)) {setHead(node);//将当前节点设置为头节点p.next null;failed false;//成功获取锁return interrupted;//自旋过程中是否被中断}//如果可以休息那么就阻塞休息if (shouldParkAfterFailedAcquire(p, node) parkAndCheckInterrupt())//如果被请求中断那么将interrupted设置为trueinterrupted true;}} finally {//自旋失败超时或被中断if (failed)//取消获取锁cancelAcquire(node);}
}//获取临界资源失败后是否可以阻塞休息
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {int ws pred.waitStatus;//前驱节点的等待标志//如果前驱节点的状态为SIGNAL那就可以阻塞了if (ws Node.SIGNAL)return true;//如果前驱节点已经放弃获取临界资源了超时、中断或被取消if (ws 0) {//找到一个正常排队的前驱节点do {node.prev pred pred.prev;} while (pred.waitStatus 0);pred.next node;//排在这个正常前驱节点的后边//如果前驱节点的状态不为SIGNAL并且没有放弃排队} else {compareAndSetWaitStatus(pred, ws, Node.SIGNAL);//将前驱节点的等待状态设置为SIGNAL}return false;//继续排队并获取临界资源
}//继续排队但停止获取临界资源并返回当前线程是否被请求中断
private final boolean parkAndCheckInterrupt() {LockSupport.park(this);return Thread.interrupted();
}//取消排队
private void cancelAcquire(Node node) {//避免空结点if (node null)return;//结点封装的线程设置为nullnode.thread null;//寻找正常排队的前驱结点Node pred node.prev;while (pred.waitStatus 0)node.prev pred pred.prev;//记录前驱节点的后继节点 Node predNext pred.next;//将当前节点的等待状态设置为CANCELLEDnode.waitStatus Node.CANCELLED;// 如果当前结点是尾结点if (node tail compareAndSetTail(node, pred)) {//直接将前驱结点之后的结点清除compareAndSetNext(pred, predNext, null);} else {//保存前驱结点的等待状态int ws;//如果前驱结点不是头节点if (pred ! head //并且前驱节点的等待状态为SIGNAL((ws pred.waitStatus) Node.SIGNAL ||//或者前驱节点正常排队并将前驱节点的等待状态成功设置为SIGNAL(ws 0 compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) //并且前驱节点的线程不为空pred.thread ! null) {Node next node.next;//如果当前节点的后继节点不为null并且正在排队if (next ! null next.waitStatus 0)//将前驱节点的后继节点设置为当前节点的后继节点compareAndSetNext(pred, predNext, next);} else {//如果前驱节点是头节点或者前驱节点的状态不是SIGNAL也无法设置为SIGNAL并且前驱节点的线程为null就唤醒当前结点的后继节点unparkSuccessor(node);}node.next node; // help GC}
}//唤醒当前结点的后继节点
private void unparkSuccessor(Node node) {//记录参数节点的状态int ws node.waitStatus;//如果参数节点正常排队if (ws 0)//将参数节点的状态设置为0compareAndSetWaitStatus(node, ws, 0);//记录参数节点的后继节点Node s node.next;//如果后继节点是null或者已取消排队if (s null || s.waitStatus 0) {s null;//遍历队列寻找一个不为null并且正常排队的结点for (Node t tail; t ! null t ! node; t t.prev)if (t.waitStatus 0)s t;}if (s ! null)//唤醒该结点LockSupport.unpark(s.thread);
}AQS的实现流程
AQS维护了状态变量和等待队列的操作方法在使用AQS框架实现同步器时只需要实现以下方法即可
protected boolean tryAcquire(int)//独占方式。尝试获取资源成功则返回true失败则返回false。
protected boolean tryRelease(int)//独占方式。尝试释放资源成功则返回true失败则返回false。
protected int tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败0表示成功但没有剩余可用资源正数表示成功且有剩余资源。
protected boolean tryReleaseShared(int)//共享方式。尝试释放资源成功则返回true失败则返回false。
protected boolean isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。以独占锁为例一个具体的实现如下
public class IdentifyLock {private static class Sync extends AbstractQueuedSynchronizer{Overrideprotected boolean tryAcquire(int arg) {if (compareAndSetState(0,arg)){setExclusiveOwnerThread(Thread.currentThread());return true;}else {return false;}}Overrideprotected boolean tryRelease(int arg) {if (getState()0||compareAndSetState(1,arg)){setExclusiveOwnerThread(null);return true;}else{return false;}}}
}AQS的使用流程
通过acquire方法就可以通过AQS的某一具体实现安全的获取临界资源该方法的源码分析如下
public final void acquire(int arg) {//通过tryAcquire尝试获取临界资源如果成功直接返回if (!tryAcquire(arg) //获取失败则通过addWaiter方法将当前线程加入等待队列并将等待队列设置为独占模式//加入队列后通过acquireQueued排队获取临界资源在排队过程中被请求中断时返回trueacquireQueued(addWaiter(Node.EXCLUSIVE), arg))//向线程发送排队过程中积攒的终端请求selfInterrupt();
}acquire方法的执行流程图如下 通过release方法就可以通过AQS的某一具体实现安全的释放临界资源该方法的源码分析如下
public final boolean release(int arg) {//使用tryRelease尝试释放临界资源如果失败直接返回if (tryRelease(arg)) {Node h head;//如果头节点不为空那就为自己并且还有后继节点if (h ! null h.waitStatus ! 0)//唤醒后继节点unparkSuccessor(h);//成功释放资源return true;}return false;
}继续完善上文的中的IdentifyLock
public class IdentifyLock {...private final static Sync SYNCnew Sync();public void lock(){SYNC.acquire(1);}public boolean unlock(){return SYNC.release(0);}
}以上分析都是在独占模式下在共享模式下过程基本相同区别在于在唤醒后继节点时如果还有剩余获取数会继续唤醒后继节点。
线程通信
void await()
boolean await(long time, TimeUnit unit)//使当前线程等待直到收到信号或被中断或指定的等待时间结束
long awaitNanos(long nanosTimeout)//返回还要等待的时间
void awaitUninterruptibly()
boolean awaitUntil(Date deadline)
void signal()
void signalAll()JDK中AQS的实现 独占式实现Lock定义了一种多条件的、可中断的、可定时的、可公平的以及可重入的锁ReentrantLock是它的一个具体的实现。ReentrantLock不仅具有与synchronized相同的功能还在此基础上提供了更高的灵活性。它与synchronized不同之处在于 ReentrantLock默认是非公平锁但可以设置为公平锁synchronized只能是非公公平锁。ReentrantLock可以与多个Condition绑定从而提供多种等待和通知条件。ReentrantLock可以在阻塞时响应中断synchronized不可以。如果在释放锁之前出现异常那么 ReentrantLock将不会被释放而synchronized在出现异常时可以自动释放对象锁。因此在使用 ReentrantLock时通常把释放锁的语句放在finally代码中。 共享式实现 Semaphore信号量一次可以允许多个线程获取临界资源。CountDownLatch倒计时锁一次可以等待多个线程访问临界资源完成。值得注意的是一个倒计时器实例只能使用一次。CyclicBarrier循环栅栏一次可以等待多个线程到达临界资源并且循环栅栏可以重复使用。 混合式实现 ReadWriteLock在一个资源可以被多个读操作访问或者被一个写操作访问但两者不能同时进行的情况下可以使用读写锁。读锁是一个共享锁不支持条件变量。写锁是一个独占锁两者都能造成死锁。它的可重入性还允许从写锁降级为读锁先获取写锁然后是读锁然后释放写锁。但是从读锁升级到写锁是不可能的。StampedLockStampedLock是读写锁的升级版它在读写锁的基础上使得读锁与写锁之间不会相互阻塞而是使用了乐观读锁的方式。它不支持条件变量和可重入。
集合安全
JUC在集合框架的基础上加入了一些含有CopyOnWrite、Concurrent以及Blocking字段的线程安全集合下文将选择具有代表性的线程安全集合进行源码分析。
#mermaid-svg-r7QpjS38NXCo58nr {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-r7QpjS38NXCo58nr .error-icon{fill:#552222;}#mermaid-svg-r7QpjS38NXCo58nr .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-r7QpjS38NXCo58nr .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-r7QpjS38NXCo58nr .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-r7QpjS38NXCo58nr .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-r7QpjS38NXCo58nr .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-r7QpjS38NXCo58nr .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-r7QpjS38NXCo58nr .marker{fill:#333333;stroke:#333333;}#mermaid-svg-r7QpjS38NXCo58nr .marker.cross{stroke:#333333;}#mermaid-svg-r7QpjS38NXCo58nr svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-r7QpjS38NXCo58nr g.classGroup text{fill:#9370DB;fill:#131300;stroke:none;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-r7QpjS38NXCo58nr g.classGroup text .title{font-weight:bolder;}#mermaid-svg-r7QpjS38NXCo58nr .nodeLabel,#mermaid-svg-r7QpjS38NXCo58nr .edgeLabel{color:#131300;}#mermaid-svg-r7QpjS38NXCo58nr .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-r7QpjS38NXCo58nr .label text{fill:#131300;}#mermaid-svg-r7QpjS38NXCo58nr .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-r7QpjS38NXCo58nr .classTitle{font-weight:bolder;}#mermaid-svg-r7QpjS38NXCo58nr .node rect,#mermaid-svg-r7QpjS38NXCo58nr .node circle,#mermaid-svg-r7QpjS38NXCo58nr .node ellipse,#mermaid-svg-r7QpjS38NXCo58nr .node polygon,#mermaid-svg-r7QpjS38NXCo58nr .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-r7QpjS38NXCo58nr .divider{stroke:#9370DB;stroke:1;}#mermaid-svg-r7QpjS38NXCo58nr g.clickable{cursor:pointer;}#mermaid-svg-r7QpjS38NXCo58nr g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-r7QpjS38NXCo58nr g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-r7QpjS38NXCo58nr .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-r7QpjS38NXCo58nr .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-r7QpjS38NXCo58nr .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-r7QpjS38NXCo58nr .dashed-line{stroke-dasharray:3;}#mermaid-svg-r7QpjS38NXCo58nr #compositionStart,#mermaid-svg-r7QpjS38NXCo58nr .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-r7QpjS38NXCo58nr #compositionEnd,#mermaid-svg-r7QpjS38NXCo58nr .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-r7QpjS38NXCo58nr #dependencyStart,#mermaid-svg-r7QpjS38NXCo58nr .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-r7QpjS38NXCo58nr #dependencyStart,#mermaid-svg-r7QpjS38NXCo58nr .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-r7QpjS38NXCo58nr #extensionStart,#mermaid-svg-r7QpjS38NXCo58nr .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-r7QpjS38NXCo58nr #extensionEnd,#mermaid-svg-r7QpjS38NXCo58nr .extension{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-r7QpjS38NXCo58nr #aggregationStart,#mermaid-svg-r7QpjS38NXCo58nr .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-r7QpjS38NXCo58nr #aggregationEnd,#mermaid-svg-r7QpjS38NXCo58nr .aggregation{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-r7QpjS38NXCo58nr .edgeTerminals{font-size:11px;}#mermaid-svg-r7QpjS38NXCo58nr :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}ListCopyOnWriteArrayListSetCopyOnWriteArraySetSortedSetNavigableSetConcurrentSkipListSetQueueConcurrentLinkedQueueDequeBlockingDequeConcurrentLinkedDequeLinkedBlockingDequeBlockingQueueTransferQueueDelayQueueLinkedBlockingQueuePriorityBlockingQueueArrayBlockingQueueSynchronousQueueLinkedTransferQueueMapSortedMapConcurrentMapNavigableMapConcurrentNavigableMapConcurrentHashMapConcurrentSkipListMapCopyOnWriteArrayList
CopyOnWriteArrayList所有读方法都不加锁只有写方法加锁并且在写方法内通过写时复制技术来保证读写写读共享也就是在写操作时先复制一个内部存储结构的副本在副本内写入之后再将原引用指向副本
public boolean add(E e) {final ReentrantLock lock this.lock;//加锁lock.lock();try {//复制底层存储结构Object[] elements getArray();int len elements.length;Object[] newElements Arrays.copyOf(elements, len 1);//将先添加元素加入副本中newElements[len] e;//将原引用指向副本setArray(newElements);return true;} finally {lock.unlock();}
}CopyOnWriteArrayList的优点在于实现了读读共享、写写互斥、读写写读共享的同步策略保证线程安全效率高缺点在于它只能保证最终结果的完整性即在过程中读操作读到的数据可能不是最新加入的数据。
ConcurrentHashMap
ConcurrentXxx并不是在每个方法上都在同一个锁同步而是使用分段锁机制来实现更大程度上的共享在这种机制下允许读操作和一定数量的写操作并发访问。ConcurrentXxx会存在弱一致性问题比如在使用迭代器迭代时虽然可以修改但是迭代的结果可能是旧的这是一种fail-safe机制。
ConcurrentSkipListMap
BlockingQueue
阻塞队列是一个可以在线程之间共享的队列。当队列为空时从队列获取元素的操作将被阻塞直到其它线程添加元素。当队列为满时向队列添加元素的操作将被阻塞直到其它线程移除元素。使用阻塞队列的好处是我们不需要关心什么时候需要阻塞线程什么时候需要唤醒线程这一切都由阻塞队列完成。 在阻塞队列中提供了以下四类API来适应不同的场景
抛出异常类
//成功添加时返回true队列已满抛出异常
//添加元素为null时抛出NullPointerException
boolean add(E e)
//成功删除返回true队列为空时抛出异常
boolean remove()
//检索但不删除队列头元素队列为空时抛出异常
E element()返回特殊值类
//成功添加时返回true队列已满返回false
//添加元素为null时抛出NullPointerException
boolean offer(E e)
//成功删除返回true队列为空返回null
E poll()
//检索但不删除队列头元素队列为空返回null
E peek()阻塞类
//成功添加时直接返回队列已满时阻塞
//添加元素为null时抛出NullPointerException
//阻塞时被中断抛出InterruptedException
void put(E e)
//队列为空时阻塞
//阻塞时被中断抛出InterruptedException
E take()超时类
//成功添加返回true队列已满时等待指定时间等待超时返回false
//添加元素为null时抛出NullPointerException
//阻塞时被中断抛出InterruptedException
boolean offer(E e, long timeout, TimeUnit unit)
//队列为空等待指定时间等待超时返回null
//阻塞时被中断抛出InterruptedException
E poll(long timeout, TimeUnit unit)JDK提供的实现类如下
ArrayBlockingQueue存储结构上基于定长数组实现。线程安全上基于ReentrantLock实现。LinkedBlockingQueue存储结构上基于链表实现默认大小为Integer.MAX_VALUE。线程安全上基于ReentrantLock实现。PriorityBlockingQueue存储结构上由基于数组的平衡二叉堆实现。线程安全上基于ReentrantLock实现。SynchronousQueue同步队列中的同步指的是当一个线程向其添加一个元素时会阻塞至线程将其取出反之亦然因此它没有提供任何空间存储元素。线程安全上基于LockSupport实现。DelayQueue底层由由PriorityBlockingQueue实现其中的元素只有在延时时间到达后才能取出。TransferQueueTransferQueue似于BlockingQueue和SynchronousQueue的组合主要体现在transfer方法当有读线程阻塞时调用transfer方法的写线程就不会将元素存入队列而是直接将元素传递给读线程如果调用transfer方法的写线程发现没有正在等待的读线程则会将元素加入队列然后会阻塞等待直到有一个读线程来获取该元素。该队列是一个接口JDK提供了一个LinkedTransferQueue实现。
void transfer(E e)
//传输一个值,或者尝试在给定超时时间内传输这个值,这个调用将阻塞,直到另一个线程将元素消费
boolean tryTransfer(E e, long timeout, TimeUnit unit)