【Java多线程】【锁策略】

目录

概念

 1、 悲观锁 VS 乐观锁

 2、轻量级锁 VS 重量级锁

 3、自旋锁 VS 挂起等待锁

 4、互斥锁 VS 读写锁

5、 公平锁 VS 非公平锁

6、可重入锁 VS 不可重入锁 


概念

锁策略是在并发编程中用于管理共享资源的一种技术。

当多个线程同时访问共享资源时,可能会出现数据竞争的情况,即多个线程同时读取或修改同一个数据,导致数据的不一致或不可预测性;

作用:通过对共享资源加锁的方式,使得每个线程在访问该资源时都必须先获取锁,然后才能进行读写操作,以保证多个线程之间的数据一致性; 

 1、 悲观锁 VS 乐观锁

锁的竞争,多个线程对一个对象加锁,会产生阻塞等待,针对这个冲突产生的概率就是实现不同锁的策略

1. 悲观锁一种较为保守的锁策略默认认为并发访问会导致冲突,因此在访问共享资源时先获取锁,然后进行操作,操作完成后再释放;

        通常使用互斥锁等阻塞锁实现;

        优点:可以避免数据冲突

        缺点:获取锁过程中需要阻塞线程,因此导致系统资源和浪费和性能的下降

        适用:并发冲突比较频繁的场景;高并发的写操作

2. 乐观锁:一种较为开放的锁策略默认认为并发访问不会导致冲突,因此在进行操作时不先获取锁,而是先读取共享资源的版本号或时间戳等标识信息,然后进行操作,操作完成后再比较标识信息是否发生了变化,未变化则提交操作,否则重新尝试

        通常使用版本号或时间戳等乐观锁控制机制实现

        优点:避免线程的阻塞,提高了系统的并发性能

        缺点:采用了CAS,可能存在ABA问题(某时间点上共享资源的标识信息发生了两次想同的变化)

        适用:并发冲突比较少;高并发的读操作

 Synchronized 加锁 初始使用乐观锁策略. 当发现锁竞争比较频繁的时候, 就会自动切换成悲观锁策略.

 2、轻量级锁 VS 重量级锁

Java中的轻量级锁和重量级锁是两种内置锁(synchronized)的实现方式,用于控制并发访问共享资源的情况。

1. 轻量级锁:是一种基于自旋锁实现的锁策略,目标是优化低并发情况下的锁操作性能;

        加锁解锁,过程更快更高效;

        优点:自旋等待,减少线程上下文切换和阻塞等待的开销,适用于低并发场景;

        缺点:自旋次数过多,或其他线程竞争激烈时,性能下降;

2. 重量级锁:基于操作系统互斥锁实现的锁策略,目标为了保证锁操作的正确性和安全性

        加锁解锁,过程更慢,更低效;

        优点:阻塞等待,避免线程饥饿和死锁问题,适用于高并发;

        缺点:线程阻塞和唤醒开销大,容易引起线程上下文切换,降低性能;

synchronized 开始是一个轻量级锁. 如果锁冲突比较严重, 就会变成重量级锁.

synchronized 中的轻量级锁策略大概率就是通过自旋锁的方式实现的.

 3、自旋锁 VS 挂起等待锁

Java中的自旋锁和挂起等待锁是两种锁的实现方式,用于控制并发访问共享资源的情况。 

1. 自旋锁:是一种忙等待的锁策略,当一个线程尝试获取锁,发现锁已经被其他线程持有,则会循环检测等待,直到其他线程释放锁;

        优点:避免线程阻塞和上下文切换的开销;

        缺点:自旋等待时间过长会浪费CPU资源,对于多核CPU自旋会导致饥饿;

        适用:锁竞争不激烈,持有时间较短的场景;线程竞争少

2. 挂起等待锁:是一种阻塞等待的锁策略,当一个线程尝试获取锁时,如果发现锁已经被其他线程持有,则会被挂起等待,直到其他线程释放锁后唤醒它。

        优点:避免CPU资源的浪费和上下文切换的开销;

        缺点:线程阻塞和唤醒的开销会降低系统性能;可能导致死锁

        适用:锁竞争激烈,持有时间较长的场景;线程竞争多;

针对上述三组策略, synchronized这把锁属于哪种呢??
        synchronized 既是悲观锁, 也是乐观锁,既是轻量级锁,也是重量级锁。轻量级锁部分基于自旋锁实现.重量级锁部分基于挂起等待锁实现~~


1. 如果锁冲突不激烈,以轻量级锁/乐观锁的状态运行

2. 如果锁冲突激烈,以重量级锁/悲观锁的状态运行

 4、互斥锁 VS 读写锁

Synchronized 是互斥锁,加锁/解锁,不是读写锁

1. 互斥锁:是一种独占锁,同一时间只允许一个线程访问共享资源,其他线程需要等待该线程释放锁后才能继续访问;适用写操作

        注意:涉及到互斥,就会产生线程等待,线程挂起,就只能等待系统再次分配;

        非必要,不加锁,提高效率

 

2. 读写锁:是一种共享锁,线程访问数据主要:读数据 和 写数据

        功能:给写加锁、给读加锁、解锁

约定:

  1. 多线程读共享数据,没有线程安全问题:读加锁间不互斥
  2. 多线程同时写共享数据,有安全问题:写加锁间互斥
  3. 一个线程写,一个线程读,有安全问题:读写加锁间互斥

        适用:频繁读,不频繁写

读写锁就是把读操作和写操作区分对待. Java 标准库提供了ReentrantReadWriteLock 类, 实现了读写锁(两个类)

  • ReentrantReadWriteLock.ReadLock 类表示一个读锁. 这个对象提供了 lock / unlock 方法进行加锁解锁.
  • ReentrantReadWriteLock.WriteLock 类表示一个写锁. 这个对象也提供了 lock / unlock 方法进行加锁解锁.

5、 公平锁 VS 非公平锁

 公平锁和非公平锁是针对锁的获取顺序而言的;

1. 公平锁:多个线程按照申请锁的顺序来获取锁,即先申请锁的线程先获取锁;

        优点:保证线程都有机会获取锁,避免线程饥饿现象;(解决死锁的一种办法)

        缺点:需要维护等待队列,公平锁的性能通常比非公平锁差

2. 非公平锁:允许抢占,多线程竞争一个锁时,不考虑申请顺序,直接抢占锁;

        优点:提高锁的吞吐量

        缺点:可能导致一些线程一直无法获取到锁,出现线程饥饿现象;

系统对于线程的调度是随机的,自带synchronized 这个锁,是非公平的;

公平锁和非公平锁没有好坏之分, 关键还是看适用场景.

6、可重入锁 VS 不可重入锁 

 可重入锁和不可重入锁是针对同一个线程在多次请求同一个锁时的情况进行分类的。

1. 可重入锁 :也称递归锁,当一个线程持有一个锁时,可以再次获取这个锁,而不是因为自己已经持有该锁而被阻塞;

        实现:需要记录当前线程已经持有该锁的次数,每次释放锁时,也需相应减少持有次数

2. 不可重入锁:也称独占锁,当一个线程持有一个锁时,其他线程不能再获取该锁;

        缺点:对于嵌套锁容易出现死锁问题

特点

可重入锁

不可重入锁

是否允许同一线程多次获取同一个锁

死锁风险

 synchronized 是可重入锁;

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值