作者:微信小助手
发布时间:2024-09-11T15:58:19
首先,我们先来看下线程安全性的定义,为什么需要锁? 线程安全,即在多线程编程中,一个程序或者代码段在并发访问时,能够正确地保持其预期的行为和状态,而不会出现意外的错误或者不一致的结果。 而解决线程安全问题,主要分为两大类:1、无锁;2、有锁。 无锁的方式有: 小白:那有锁的方式呢,怎么通过加锁保证线程安全呢? 别急哈,下面听我给你一一道来。 从加锁的策略看,分为隐式锁和显示锁。隐式锁通过 Synchronized 实现,显示锁通过 Lock 实现。 乐观锁实现:CAS,比较并交换,通常指的是这样一种原子操作:针对一个变量,首先比较它的内存值与某个期望值是否相同,如果相同,就给它赋一个新值。 但是,这一篇我们主要来看下悲观锁的一些常用实现。 syncronized 是 Java 中的一个关键字,用于控制对共享资源的并发访问,从而防止多个线程同时访问某个特定资源,这被称为同步。这个关键字可以用来修饰方法或代码块。 syncronized 使用对象锁保证临界区内代码的原子性 小白:synchronized 的底层原理是什么呀,怎么自己就完成加锁释放锁操作了? 其实 synchronized 的原理也不难,主要有以下两个关键点。 对象头主要包括标记字段 Mark World,元数据指针,如果是数组对象的话,对象头还必须存储数组长度。 synchronized 也是基于此,通过锁对象的 monitor 获取和 monitor 释放来实现,对象头标记为存储具体锁状态,ThreadId 记录持有偏向锁的线程 ID。 这里,又引申另外出一个问题:你知道什么是偏向锁呢? 小白:不知道,啥玩意? synchronized 锁升级过程 说到这里,那就不得不提及 synchronized 的锁升级机制了,因为 synchronized 的加锁释放锁操作会使得 CPU 在内核态和户态之间发生切换,有一定性能开销。在 JDK1.5 版本以后,对 synchronized 做了锁升级的优化,主要利用轻量级锁、偏向锁、自适应锁等减少锁操作带来的开销,对其性能做了很大提升。 小白:哦哦原来如此,那刚刚你说了 Java 除了隐式锁之外,还有显示锁呢? 在 Java 中,除了对象锁,还有显示的加锁的方式,比如 Lock 接口,用得比较多的就是 ReentrantLock。它的特性如下: 下面我们再来对比看下 ReentrantLock 和 synchronized 的区别 从这些对比就能看出 ReentrantLock 使用更加的灵活,特性更加丰富。 ReentrantLock 是一个悲观锁,即是同一个时刻,只允许一个线程访问代码块,这一点 synchronized 其实也一样。 小白:这个是挺好用的,但是我们有一些读多写少的场景中比如缓存,大部分时间都是读操作,这里每个操作都要加锁,读性能不是很差吗,有没有更好的方案实现这种场景呀? 当然有的,比如 ReentrantReadWriteLock,读写锁。 针对上述场景,Java 提供了读写锁 ReentrantReadWriteLock,它的内部维护了一对相关的锁,一个用于只读操作,称为读锁;一个用于写入操作,称为写锁。 使用核心代码如下 在 ReentrantReadWriteLock 中,多个线程可以同时读取一个共享资源。 当有其他线程的写锁时,读线程会被阻塞,反之一样。 读写锁设计思路 这里有一个关键点,就是在 ReentrantLock 中,使用 AQS 的 state 表示同步状态,表示锁被一个线程重复获取的次数。但是在读写锁 ReentrantReadWriteLock 中,如何用一个变量维护这两个状态呢? 实际 ReentrantReadWriteLock 采用“高低位切割”的方式来维护,将 state 切分为两部分:高 16 位表示读;低 16 位表示写。 分割之后,通过位运算,假设当前状态为 S,那么: 这时,我们再来思考下,如果有线程正在读,写线程需要等待读线程释放锁才能获取锁,也就是读的时候不允许写,那么有没有更好的方式改进呢? 小白:emm,这个真的难倒我了。。。。。。 哈哈莫慌,Java8 已经引入了新的读写锁,StampedLock。它和 ReentrantReadWriteLock 相比,区别在于读过程允许获取写锁写入,在原来读写锁的基础上加了一种乐观锁机制,该模式不会阻塞写锁,只是最后会对比原来的值,有着更高的并发性能。 StampedLock 三种模式如下: 小白:那这里可以允许多个读操作和也给写线程同时进入共享资源操作,那读取的数据被改了怎么办啊?? 别担心,乐观读不能保证读到的数据是最新的,所以当把数据读取到局部变量的时候需要通过 lock.validate 方法来校验是否被修改过,如果是改过了那么就加上悲观读锁,再重新读取数据到局部变量。
Java 有哪些锁?
syncroized 是什么?
ReentrantLock 简介
ReentrantReadWriteLock 介绍
/** Inner class providing readlock */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock */
private final ReentrantReadWriteLock.WriteLock writerLock;
/** Performs all synchronization mechanics */
final Sync sync;public class LocalCacheService {
static Map<String, Object> localCache = new HashMap<>();
static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
static Lock readL = lock.readLock();
static Lock writeL = lock.writeLock();
public static Object read(String key) {
readL.lock();
try {
return localCache.get(key);
} finally {
readL.unlock();
}
}
public static Object save(String key, String value) {
writeL.lock();
try {
return localCache.put(key, value);
} finally {
writeL.unlock();
}
}
}
什么是 StampedLock?