1.线程同步机制简介
导致线程安全问题的因素更多的是侧重其根源,包括硬件(写缓冲器)和软件(编译器),但从应用程序的角度来看,线程安全问题的产生是由于多线程应用程序缺乏某种东西,即【线程同步机制】
线程同步机制:是一套用于协调线程间的数据访问和活动的机制,该机制用于保障线程安全以及实现这些线程的共同目标
举例:线程=公路上行驶的车辆,线程同步机制=大家都需要遵守的交通规则

2.锁概述
2.1锁产生的背景思路
线程安全问题的产生前提:多个线程并发访问共享变量、共享资源
保证线程安全的解决思路:将多个线程对共享数据的访问转换为串行访问,即一个共享数据一次只能被一个线程访问,该线程访问结束后其他线程才能对其进行访问
- 锁的诞生就是利用这种思路来保障线程安全的,它相当于一个【许可证】
2.2获得锁、释放锁、临界区、互斥锁(Mutex)


2.3内部锁、显式锁
这是按照Java虚拟机对锁的实现方式进行的划分
- 内部锁(Intrinsic Lock):通过
**synchronized**关键字实现
- 显式锁(Explici Lock):通过
**java.concurrent.locks.Lock**接口的实现类(如java.concurrent.locks.ReentrantLock类)实现的
2.4锁的作用
三大作用
保障原子性:通过互斥性,一个锁一次只能被一个线程持有,这就保障了临界区代码一次只能被一个线程执行,其他线程只能等待其释放该锁后再申请。就好比多股车道在某处被合并为一股车道,齐驾齐驱的车辆只能“鱼贯而行”
保障可见性:
可见性的保障是通过写线程冲刷处理器缓存和读线程刷新处理器缓存这两个动作实现的
在Java中,
- 锁的获得隐含着**刷新**处理器缓存这个动作,这使得读线程在执行临界区代码之前,可以将写线程对共享变量所做的更新同步到该线程执行处理器的高速缓存中。
- 锁的释放隐含着冲刷处理器缓存这个动作,这使得写线程对共享变量所做的更新能够被“推送”到该线程执行处理器的高速缓存中,从而对读线程可同步。
- 保障有序性:
注意:尽管锁能够保障有序性,但这并不意味着临界区内的内存操作不能够被重排序
锁保证三大作用的条件


*拓展阅读


2.5与锁相关的几个概念
可重入性


重点扩展
锁的争用和调度
锁可以被看做多线程程序访问共享数据时所需持有的一种排他性资源

锁的粒度
一个锁的实例可以保护一个或者多个共享数据
一个锁实例所保护的共享数据的数量大小就被称为该锁的粒度
一个锁实例保护的共享数据的数量大,则该锁粒度粗,否则该锁粒度细
- 锁的粒度过细会增加锁调度的开销
2.6锁的开销及其可能导致的问题

3.内部锁:Synchronized关键字
Java平台中的任何一个对象都有唯一一个与之相关的锁,这种锁被称为监视器或者内部锁
内部锁是一种排他锁,它能够保证原子性、可见性和有序性
它是通过Synchronized关键字来实现的
Synchronized(锁句柄){//在此代码块中访问共享数据}


以上可以改写为


小Tip:
拓展阅读—内部锁的调度

4.显式锁:Lock接口
显式锁是自JDK1.5开始引入的排它锁,作用与内部锁相同,还提供了一些内部锁不具备的特性
4.1使用

步骤解析
4.2显式锁的调度
4.3显式锁和内部锁的比较
4.4锁的选用
4.5改进型锁:读写锁







5.锁的适用场景

6.内存屏障




7.锁与重排序


无论是编译器还是处理器,均需要遵守以下重排序规则
- 临界区内的操作不允许被重排序到临界区之外(即临界区前或者临界区后)
- 临界区内的操作之间允许被重排序
- 临界区外的操作之间可以被重排序
- 锁申请(MonitorEnter)与锁释放(MonitorExit)操作不能重排序,以此确保锁的申请与释放总是配对的
- 两个锁申请操作不能被重排序
- 两个锁释放操作不能被重排序
- 临界区外的操作可以被重排到临界区之内
8.轻量级同步机制:volatile关键字

8.1 volatile的作用
注意:
为什么volatile具有保障有序性和可见性的作用?









