一、锁的概念
JAVA存在多种锁,锁存在的意义就是保证共享资源在多线程情况下的安全问题。
锁的实现有多种,而关于锁的定义可以分为如下几种:
- 自旋锁
- 悲观锁
- 乐观锁
- 独享锁(写锁)
- 共享锁(读锁)
1.1、自旋锁
1.1.1、介绍
自旋锁,通过死循环,不断使用 CAS 对共享数据进行尝试修改操作,直至成功。
自旋锁实现的两个重要点:
- 不断循环操作
- 修改操作使用 CAS 保证线程安全。
1.1.2、简单代码实现
LockDemo.java
关键代码:1.2、悲观锁
1.2.1、介绍
假定在对共享数据a进行操作时,认为一定会有其他线程已经修改过了共享数据a。
为了保证操作的线程安全问题,在对共享数据a进行操作的过程中,全程加锁。从读数据开始就开始加锁。
1.2.2、悲观锁的实现
synchronized 关键字实现锁就是一个悲观锁,被锁定的区域范围内的操作都是同步的。
1.3、乐观锁
1.3.1、介绍
假定对共享数据a进行操作时,认定共享数据一定没有被其他线程过,尝试进行数据修改。
乐观锁的实现,类似 CAS,或者通过版本号来保证数据修改的线程安全。(假定:读取到了共享数据a=2.进行累加操作。a+1=3 时,先判定 此时数据是否=2,如果是,则进行修改,反之,修改失败(循环重试便成了自旋锁))
1.4、独享锁(写)和共享锁(读)
共享数据在只有读操作的时候不存在安全性问题。只有当读写共存的时候才会发生线程安全问题。
参考:读写分离锁实现
二、其他锁概念
2.1、可重入锁(不可重入锁)
锁的作用是锁定共享资源,保证多线程操作时,数据的一致性问题。
在代码中,可能存在多个共享资源,它们使用同一把锁定资源。
如:假定存在两个操作,这两个操作使用通过一个锁锁定。代码如下:
method1 和 method2 使用相同的锁 class 锁锁定资源。
测试方法 main 方法启动时,启动的线程为 main 线程,main 线程访问 method1 方法时获得到了 class 锁,在 method1 中调用 method2 ,因为 method1和method2使用相同的锁,所以 main 线程能够直接访问 method2 ,这个叫做锁的重入。反之,如果 method2 持有的不是 class 锁,而是 this 对象锁,则需要获取到另一把锁。
2.2、偏向锁、轻量级锁、重量两级锁
这些概念在 synchronized 中。
2.3、公平锁、非公平锁
happen before 原则中提到 lock 需要在 unlock 操作之后,即存在多个锁争抢时,需要等到锁持有的线程解锁调用 unlock 后,才能去争抢锁,尝试执行 lock 操作。
在争抢的过程中,先来等待的线程先抢锁,按照先来后到的方式获取锁的为公平锁。反之,为非公平锁。