写在开头
提起Java领域中的锁,是否有种“道不尽红尘奢恋,诉不完人间恩怨“的”感同身受“之感?细数那些个“玩意儿”,你对Java的热情是否还如初恋般“人生若只如初见”?
Java中对于锁的实现真可谓是“百花齐放”,按照编程友好程度来说,美其名曰是Java提供了种类丰富的锁,每种锁因其特性的不同,在适当的场景下能够展现出非常高的效率。
但是,从理解的难度上来讲,其类型错中复杂,主要原因是Java是按照是否含有某一特性来定义锁的实现,如果不能正确理解其含义,了解其特性的话,往往都会深陷其中,难可自拔。
查询过很多技术资料与相关书籍,对其介绍真可谓是“模棱两可”,生怕我们搞懂了似的,但是这也是我们无法绕过去的一个“坎坎”,除非有其他的选择。
作为一名Java Developer来说,正确了解和掌握这些锁的机制和原理,需要我们带着一些实际问题,通过特性将锁进行分组归类,才能真正意义上理解和掌握。
比如,在Java领域中,针对于不同场景提供的锁,都用于解决什么问题?其实现方式是什么?各自又有什么特点,对应的应用有哪些?
带着这些问题,今天我们就一起来盘一盘,Java领域中的锁机制,盘点一下相关知识点,以及不同的锁的适用场景,帮助我们更快捷的理解和掌握这项必备技术奥义。
关健术语
本文用到的一些关键词语以及常用术语,主要如下:
线程调度(Thread Scheduling ):系统分配处理器使用权的过程,主要调度方式有两种,分别是协同式线程调度(Cooperative Threads-Scheduling)和抢占式线程调度(Preemptive Threads-Scheduling)。
线程切换(Thread Switch ):主要是指在并发过程中,多线程之间会对上下文进行切换资源,并交叉执行的一种并发机制。
指令重排(Command Reorder ): 指编译器或处理器为了优化性能而采取的一种手段,在不存在数据依赖性情况下(如写后读,读后写,写后写),调整代码执行顺序。
内存屏障(Memory Barrier): 也称内存栅栏,内存栅障,屏障指令等, 是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作。
基本概述
纵观Java领域中“五花八门”的锁,我们可以依据Java内存模型的工作机制,来具体分析一下对应问题的提出和表现,这也不失为打开Java领域中锁机制的“敲门砖”。
从本质上讲,锁是一种协调多个进程 或者多个线程对某一个资源的访问的控制机制。
一.计算机运行模型
计算机运行模型主要是描述计算机系统体系结构的基本模型,一般主要是指CPU处理器结构。
在计算机体系结构中,中央处理器(CPU,Central Processing Unit)是一块超大规模的集成电路,是一台计算机的运算核心(Core)和控制核心( Control Unit)。它的功能主要是解释计算机指令以及处理计算机软件中的数据。
一个计算能够运行起来,主要是依靠CPU来负责执行我们的输入指令的,通常情况下,我们都把这些指令统称为程序。
一般CPU决定着程序的运行速度,可以看出CPU对程序的执行有很重要的作用,但是一个计算机程序的运行快慢并不是完全由CPU决定,除了CPU还有内存、闪存等。
由此可见,一个CPU主要由控制单元,算术逻辑单元和寄存器单元等3个部分组成。其中:
控制单元( Control Unit): 属于CPU的控制指挥中心,主要负责指挥CPU工作,通过向算术逻辑单元和寄存器单元来发送控制指令达到控制效果。
算术逻辑单元(Arithmetic Logic Unit, ALU): 主要负责执行运算,一般是指算术运算和逻辑运算,主要是依据控制单元发送过来的指令进行处理。
寄存器单元(Register Unit): 主要用于存储临时数据,保存着等待处理和已经处理的数据。
一般来说,寄存器单元是为了减少CPU对内存的访问次数,提升数据读取性能而提出的,CPU中的寄存器单元主要分为通用寄存器和专用寄存器两个种,其中:
1.通用寄存器:主要用于临时存放CPU正在使用的数据。
2.专用寄存器:主要用于临时存放类似指令寄存器和程序计数器等CPU中专有用途的数据。其中:
指令寄存器:用于存储正在执行的指令
程序计数器: 保存等待执行的指令地址
简单来说,CPU与主存储器主要是通过总线来进行通信,CPU通过控制单元来操作主存中的数据。而CPU与其他设备的通信都是由控制来实现。
综上所述,我们便可以得到一个计算机内存模型的大致雏形,接下来,我们便来一起盘点解析是计算机内存模型的基本奥义。
二.计算机内存模型
计算机内存模型一般是指计算系统底层与编程语言之间的约束规范,主要是描述计算机程序与共享存储器访问的行为特征表现。
根据介绍计算机运行模型来看,计算机内存模型可以帮助以及指导我们理解Java内存模型,主要在如下的两个方面:
首先,系统底层希望能够对程序进行更多的优化策略,一般主要是针对处理器和编译器,从而提高运行性能。
其次,为编程语言带来了更多的可编程性问题,主要是复杂的内存模型会有更多的约束,从而增加了程序设计的编程难度。
由此可见,内存模型用于定义处理器间的各层缓存与共享内存的同步机制,以及线程与内存之间交互的规则。
在操作系统层面,内存主要可以分为物理内存与虚拟内存的概念,其中:
1.物理内存(Physical Memory): 通常指通过安装内存条而获得的临时储存空间。主要作用是在计算机运行时为操作系统和各种程序提供临时储存。常见的物理内存规格有256M、512M、1G、2G等。
2.虚拟内存(Virtual Memory):计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续可用的内存(一个连续完整的地址空间),它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。
一般情况下,当物理内存不足时,可以用虚拟内存代替, 在虚拟内存出现之前,程序寻址用的都是物理地址。
从常见的存储介质来看,主要有:寄存器(Register),高速缓存(Cache),随机存取存储器(RAM),只读存储器(ROM)等4种,按照读取快慢的顺序是:Register>Cache>RAM>ROM。其中:
1.寄存器(Register): CPU处理器的一部分,主要分为通用寄存器和专用寄存器。
2.高速缓存(Cache):用于减少 CPU 处理器访问内存所需平均时间的部件,一般是指L1/L2/L3层高级缓存。
3.随机存取存储器(Random Access Memory,RAM):与CPU直接交换数据的内部存储器,它可以随时读写,而且速度很快,通常作为操作系统或其他正在运行中的程序的临时数据存储媒介。
4.只读存储器(Read-Only Memory,ROM):所存储的数据通常都是装入主机之前就写好的,在工作的时候只能读取而不能像随机存储器那样随便写入。
由于CPU的运算速度比主存(物理内存)的存取速度快很多,为了提高处理速度,现代CPU不直接和主存进行通信,而是在CPU和主存之间设计了多层的Cache(高速缓存),越靠近CPU的高速缓存越快,容
量也越小。
按照数据读取顺序和与CPU内核结合的紧密程度来看,大多数采用多层缓存策略,最经典的就三层高速缓存架构。
也就是我们常说的,CPU高速缓存有L1和L2高速缓存(即一级高速缓存和二级缓存高速),部分高端CPU还具有L3高速缓存(即三级高速缓存):