在计算机系统中,锁(lock)或互斥(mutex)是一种同步机制,用于在有许多执行线程的环境中强制对资源的访问限制。锁旨在强制实施互斥排他、并发控制策略。

多数情况下,锁都是硬件指令运行的。

锁相关的概念

线程间通信的四个方法,临界区,互斥量,信号量,事件。

锁的种类

Java提供了种类丰富的锁,每种锁因其特性的不同,在各自适当的场景下能够展现出非常高的效率。

java各大框架学习笔记教程(Java基础多线程)(1)

宏观的锁类型编译器对锁的底层优化

JIT编译器(Just In Time编译器)可以在动态编译同步代码时,使用一种叫做逃逸分析的技术,可以判别程序中所使用的锁对象与线程的关系。

线程等待策略

对未获得锁的线程等待策略进行优化,来避免操作系统进程调度和线程切换带来的开销:

计算机科学家们使用了各种方式来实现排队自旋锁,如TicketLock,MCSLock,CLHLock。

线程获取锁的策略

为了减少唤起线程的开销,让代码整体的吞吐效率高,有一种优化策略是,让线程上来就直接尝试占有锁,如果尝试失败,就再去排队,这就是非公平锁

普通的公平锁的策略是,每个线程在获取锁时会先查看此锁维护的等待队列,如果有等待,则会加入到等待队列中。此队列是FIFO的规则。

非公平锁策略因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程。所以开销小,效率高。但是同时可能让已经在等待中的线程等待太久。

Java并发包中的ReentrantLock,默认情况下是非公平锁,如果使用new ReentrantLock(true),则是公平锁。synchronized 也是非公平锁。

锁的状态

目前锁一共有4种状态,级别从低到高依次是:无锁、偏向锁、轻量级锁和重量级锁。锁状态只能升级不能降级。

synchronized通过Monitor来实现线程同步,Monitor是依赖于底层的操作系统的Mutex Lock(互斥锁)来实现的线程同步。

四种锁状态对应的的Mark Word内容:

锁状态

存储内容

存储内容

无锁

对象的hashCode、对象分代年龄、是否是偏向锁(0)

01

偏向锁

偏向线程ID、偏向时间戳、对象分代年龄、是否是偏向锁(1)

01

轻量级锁

指向栈中锁记录的指针

00

重量级锁

指向互斥量(重量级锁)的指针

10

优化锁的获得过程

同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁,不会因为之前已经获取过还没释放而阻塞,可一定程度避免死锁。这就是可重入锁又名递归锁。

相对来说,仍然需要获取而阻塞则是非可重入锁

Java并发包中的ReentrantLock和synchronized都是可重入锁,NonReentrantLock是非可重入锁。

可重入锁实现可重入性原理或机制是:每一个锁关联一个线程持有者和计数器,计数器为0时可调用并标识为1,其他线程将等待,退出时状态递减,状态为0时释放锁。同一个线程多数获取则状态递增。

对锁的占用方案

并非所有场景都是只允许资源只能被一个线程独占。所以出现了可以被多个线程所共有的共享锁

在ReentrantReadWriteLock里面,读锁和写锁是分离的,读锁是共享锁,写锁是独享锁。读锁的共享锁可保证并发读非常高效,而读写、写读、写写的过程互斥。

独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。

抽象队列同步器(AbstractQueuedSynchronizer,简称AQS)是用来构建锁或者其他同步组件的基础框架,它使用一个整型的volatile变量(命名为state)来维护同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。

Java对象附加的锁数据

主要包括两部分数据:Mark Word(标记字段)、Klass Pointer(类型指针)。

Mark Word:默认存储对象的HashCode,分代年龄和锁标志位信息。这些信息都是与对象自身定义无关的数据,所以Mark Word被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据。它会根据对象的状态复用自己的存储空间,也就是说在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化。

Klass Point:对象指向它的类的元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

Monitor可以理解为一个同步工具或一种同步机制,通常被描述为一个对象。每一个Java对象就有一把看不见的锁,称为内部锁或者Monitor锁。

Monitor是线程私有的数据结构,每一个线程都有一个可用monitor record列表,同时还有一个全局的可用列表。每一个被锁住的对象都会和一个monitor关联,同时monitor中有一个Owner字段存放拥有该锁的线程的唯一标识,表示该锁被这个线程占用。

,