「这是我参与11月更文挑战的第22天,活动详情查看:2021最后一次更文挑战」
1. 乐观锁和悲观锁
这个是从并发角度来分类的,当一个数据被多个线程所共同使用,且线程并发执行时,我们需要保证保证该数据的准确性,既一个线程对数据的操作不会对另一个线程产生不合理的影响。
- 对于同一个数据的并发操作,悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。Java中,synchronized关键字和Lock的实现类都是悲观锁。
- 乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作,例如报错或者自动重试。
2. 公平锁、非公平锁
- 公平锁指多个线程按照申请锁的顺序来获取锁。
- 非公平锁就是没有顺序完全随机,所以能会造成优先级反转或者饥饿现象。
- synchronized 就是非公平锁,ReentrantLock(使用 CAS 和 AQS 实现)通过构造参数可以决定是非公平锁还是公平锁,默认构造是非公平锁;非公平锁的吞吐量性能比公平锁大好。
3. 独享锁、共享锁
- 独享锁是指该锁一次只能被一个线程持有。
- 共享锁指该锁可以被多个线程持有。
- synchronized 和 ReentrantLock 都是独享锁,ReadWriteLock 的读锁是共享锁,写锁是独占锁; ReentrantLock 的独享锁和共享锁也是通过 AQS 来实现的。
4. 自旋锁
如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。
5. 偏向锁、轻量级锁、重量级锁
- 偏向锁:它会偏向于第一个访问锁的线程,如果在运行过程中,同步锁只有一个线程访问,不存在多线程争用的情况,则线程是不需要触发同步的,减少加锁/解锁的一些CAS操作(比如等待队列的一些CAS操作),这种情况下,就会给线程加一个偏向锁。 如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会消除它身上的偏向锁,将锁恢复到标准的轻量级锁。它通过消除资源无竞争情况下的同步原语,进一步提高了程序的运行性能。
- 轻量级锁:轻量级锁是由偏向锁升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁。
- 重量级锁:当竞争线程尝试占用轻量级锁失败多次之后,轻量级锁就会膨胀为重量级锁,重量级线程指针指向竞争线程,竞争线程也会阻塞,等待轻量级线程释放锁后唤醒他。