锁的分类:
一,线程是否需要锁住同步资源
广义角度上的概念,对于同一个数据的并发操作下
-
乐观锁
- 认为自己使用数据的时候,不会有别的线程来修改数据,只是会在更新数据的时候,判断数据是否被修改过,如果没有修改过,则直接读取数据操作,否则根据不同的实现方式来执行不同的操作(重试或者报错)。
- 乐观锁常用无锁编程来实现,最常采用的方法就是CAS算法,Java的原子类就是通过CAS算法来实现的。
- 适合读较多的场景
-
乐观锁的场景
AtomicInteger i = new AtomicInteger(1); i.incrementAndGet();
这里涉及到一个原子类AtomicInteger的CAS算法实现,具体可以看这篇文章AtomicInteger实现原理
-
悲观锁
- 认为自己使用数据的时候,一定会有其他线程对数据进行了更新操作,所以获取数据的时候一定要先加锁,确保数据不会被修改。
- Java中的synchornized关键字和Lock的子类实现都是悲观锁。
- 适合写较多的场景。
-
悲观锁的场景-(需要保证多个线程使用的是同一个锁)
方式一
private ReentrantLock lock = new ReentrantLock();
private void doLock() {
//尝试获取锁
lock.lock();
//doSomeThing();
lock.unlock();
//执行完unLock释放锁
}
方式二
private Object syncLock = new Object();
private void syncLock() {
//尝试获取锁
synchronized (syncLock) {
doSomeThing();
}
//执行完同步块,自动释放锁
}
二,锁住同步资源,线程是否需要阻塞
- 阻塞
- 不阻塞
介绍自旋锁之前,我们先说明一个概念,Java线程的阻塞和唤醒,都是需要操作系统来切换CPU状态来实现的,这种状态切换需要耗费处理器时间,如果同步块的操作的内容过于简单,状态转换的耗时可能比执行操作的时间更长
- 自旋锁
- 不放弃CPU时间片,通过自旋等待锁释放,降低因CPU状态切换的的消耗
- 自旋获取成功之后,操作同步资源
- 失败的情况下,CPU切换状态,线程休眠
- 适应性自旋锁
- 意味这自旋的时间和次数都不在固定
- 而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定
- 如果对于同一个锁对象上,自旋刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋也是很有可能再次成功,进而它将允许自旋等待持续相对更长的时间
- 如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源。
三,多个线程竞争同步资源的流程细节有没有区别
这四种锁是指锁的状态,专门针对synchronized的
首先为什么Synchronized能实现线程同步?
- 无锁
不锁住资源,多个线程只有一个能修改资源成功,其他的会重试(主要实现CAS算法) - 偏向锁
同一个线程操作执行同步资源时自动获得资源 - 轻量级锁
多个线程竞争同步资源时,没有获得资源的线程自旋或者等待资源释放 - 重量级锁
多个线程竞争同步资源时,没有获得资源的线程阻塞等待唤醒
所以目前锁一共有4种状态,级别从低到高依次是:无锁、偏向锁、轻量级锁和重量级锁。锁状态只能升级不能降级。
四,多个线程竞争资源要不要排队
-
公平锁 需要排队
- 多线程根据申请锁的顺序来获取锁
- 优点是等待锁的线程不会饿死
- 缺点是整体的吞吐效率相对非公平锁要低
- 等待队列的线程都需要CPU唤醒阻塞线程,开销比较大
-
非公平锁 先尝试插队,失败了再排队
- 申请锁的线程,先尝试获取锁,失败了再排队
- 优点是整体的吞吐效率比较好,可以减少唤醒线程的开销
- 缺点是有线程可能会饿死或者等很久才能获得锁
-
ReentrantLock 公平和非公平锁的实现方式
-
ReentrantLock里面有一个内部类Sync,Sync继承AQS(AbstractQueuedSynchronizer),添加锁和释放锁的大部分操作实际上都是在Sync中实现的
-
Sync有公平锁FairSync和非公平锁NonfairSync两个子类,公平锁和非公平锁的实现区别就是
public final boolean hasQueuedPredecessors() { // The correctness of this depends on head being initialized // before tail and on head.next being accurate if the current // thread is first in queue. Node t = tail; // Read fields in reverse initialization order Node h = head; Node s; return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); }
-
如上可以看出该方法主要做一件事情:主要是判断当前线程是否位于同步队列中的第一个。如果是则返回true,否则返回false。
综上,公平锁就是通过同步队列来实现多个线程按照申请锁的顺序来获取锁,从而实现公平的特性。非公平锁加锁时不考虑排队等待问题,直接尝试获取锁,所以存在后申请却先获得锁的情况。
五,一个线程中的多个流程能否获得同一把锁
-
可重入锁 :能 可重入锁又名递归锁
-
指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),不会因为之前已经获取过还没释放而阻塞。
-
Java中ReentrantLock和synchronized都是可重入锁,
-
可重入锁的一个优点是可一定程度避免死锁。
-
可重入锁示例
private synchronized void doOne(String a) { doOnther(a); } private synchronized void doOnther(String b) { System.out.println("b = [" + b + "]"); }
-
-
非可重入锁 :不能
六,多个线程能否共享一把锁
- 共享锁 :能
- 排它锁 :不能
其他文章
网友评论