博主总结
-
1、各种锁全面快速了解
- 悲观锁(排他锁、互斥锁),上下文切换频繁。适合并发量高、对读写资源竞争高的场景
- 乐观锁(自旋锁,循环实现CAS机制),读多写少且竞争小的。轻量,避免锁竞争。因此在读取数据时不加锁,而是在更新数据时通过CAS机制来验证数据是否被其他线程修改过。ABA问题是在使用CAS时可能出现的一种并发问题。在多线程环境下,如果一个变量的值先被线程A修改为B,然后又被线程B修改回A,那么在使用CAS进行比较和交换操作时,尽管变量的当前值与预期值相同(都是A),但实际上这个变量的值已经被修改过,这就是ABA问题。
- 公平锁(先来后到)、非公平锁(本线程优先获取锁, synchornized)
- 排他锁和共享锁,可以看作写锁(会造成线程安全问题)和读锁(不会造成线程安全问题,会共享起来提升读的性能,可结合Volatile保证可见性)
- 可重入锁(递归锁),可重入锁其实就是同一个线程持有的锁,synchronized和ReentranLock具有可重入锁特性,可重复持有的锁

- 锁升级:无锁 -> 偏向锁(可看作可重入锁) -> 轻量级锁 (可看作乐观锁)-> 重量级锁(悲观锁(自旋锁),涉及到从应用态切换到内核态),它们是synchornized底层所升级的四种状态,synchornized随着并发量的增加锁的重量就会增加,锁越重性能越低但就越安全。
-
2、悲观锁和乐观锁详解
- 悲观锁与乐观锁是什么?
- 悲观锁:共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程。synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
- 乐观锁:无需加锁也无需等待,只是在提交修改的时候去验证对应的资源(也就是数据)是否被其它线程修改了(具体方法可以使用版本号机制或 CAS 算法)。在 Java 中java.util.concurrent.atomic包下面的原子变量类(比如AtomicInteger、LongAdder)就是使用了乐观锁的一种实现方式 CAS 实现的。
- LongAdder:它在内部维护了多个计数器(通常使用数组),每个线程可以独立地对自己的计数器进行更新,从而减少竞态条件。只有在需要计算总和时,这些计数器的值才会被汇总。因此,它在高并发场景下能够提供更好的性能。
- 悲观锁和乐观锁如何选用?
- 悲观锁通常多用于写比较多的情况(多写场景,竞争激烈),这样可以避免频繁失败和重试影响性能,悲观锁的开销是固定的。不过,如果乐观锁解决了频繁失败和重试这个问题的话(比如LongAdder),也是可以考虑使用乐观锁的,要视实际情况而定。
- 乐观锁通常多用于写比较少的情况(多读场景,竞争较少),这样可以避免频繁加锁影响性能。不过,乐观锁主要针对的对象是单个共享变量(参考java.util.concurrent.atomic包下面的原子变量类)。
- 乐观锁的实现(CAS和版本号法,CAS相对使用较多)
- 版本号法:一般是在数据表中加上一个数据版本号 version 字段,表示数据被修改的次数。当数据被修改时,version 值会加一。当线程 A 要更新数据值时,在读取数据的同时也会读取 version 值,在提交更新时,若刚才读取到的 version 值为当前数据库中的 version 值相等时才更新,否则重试更新操作,直到更新成功。
-
3、synchronized和volatile区别?
- synchronized 关键字和 volatile 关键字是两个互补的存在,而不是对立的存在!
- volatile 关键字是线程同步的轻量级实现,所以 volatile性能肯定比synchronized关键字要好 。但是 volatile 关键字只能用于变量而 synchronized 关键字可以修饰方法以及代码块 。
- volatile 关键字能保证数据的可见性,但不能保证数据的原子性。synchronized 关键字两者都能保证。
- volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized 关键字解决的是多个线程之间访问资源的同步性。
-
4、synchronized和reentrantLock区别?
- synchronized依赖于JVM,而RentrantLock依赖于JDK的API实现。
- ReentrantLock相较于sync提供很多功能增强。
- 等待可中断 : ReentrantLock提供了一种能够中断等待锁的线程的机制,通过 lock.lockInterruptibly() 来实现这个机制。也就是说当前线程在等待获取锁的过程中,如果其他线程中断当前线程「 interrupt() 」,当前线程就会抛出 InterruptedException 异常,可以捕捉该异常进行相应处理。
- 可实现公平锁 : ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。ReentrantLock默认情况是非公平的,可以通过 ReentrantLock类的ReentrantLock(boolean fair)构造方法来指定是否是公平的。
- 可实现选择性通知(锁可以绑定多个条件): synchronized关键字与wait()和notify()/notifyAll()方法相结合可以实现等待/通知机制。ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition()方法。
- 支持超时 :ReentrantLock 提供了 tryLock(timeout) 的方法,可以指定等待获取锁的最长等待时间,如果超过了等待时间,就会获取锁失败,不会一直等待。
-
5、公平锁和非公平锁区别?
- 公平锁 : 锁被释放之后,先申请的线程先得到锁。性能较差一些,因为公平锁为了保证时间上的绝对顺序,上下文切换更频繁。
- 非公平锁:锁被释放之后,后申请的线程可能会先获取到锁,是随机或者按照其他优先级排序的。性能更好,但可能会导致某些线程永远无法获取到锁。
-
6、可中断锁和不可中断锁区别?
- 可中断锁:获取锁的过程中可以被中断,不需要一直等到获取锁之后 才能进行其他逻辑处理。ReentrantLock 就属于是可中断锁。
- 不可中断锁:一旦线程申请了锁,就只能等到拿到锁以后才能进行其他的逻辑处理。 synchronized 就属于是不可中断锁。
-
7、共享锁和独占锁区别?
-
8、并发工具类
- CountDownLatch 是 JUC 中的一个同步工具类,用于协调多个线程之间的同步,确保主线程在多个子线程完成任务后继续执行。
- CyclicBarrier 的字面意思是可循环使用的屏障,用于多个线程相互等待,直到所有线程都到达屏障后再同时执行。
- Semaphore——信号量,用于控制同时访问某个资源的线程数量,类似限流器,确保最多只有指定数量的线程能够访问某个资源,超过的必须等待。

- Exchanger——交换者,用于在两个线程之间进行数据交换。
- CyclicBarrier和CountDownLatch区别?
