【并发】锁的品种为什么辣么多

目录

1.悲观锁 VS 乐观锁

2.公平锁 VS 非公平锁

3.独占锁 VS 共享锁

4.可重入锁

5. 自旋锁


1.悲观锁 VS 乐观锁

互斥同步属于一种悲观的并发策略

其总是认为只要不去做正确的同步措施(例如加锁),那就肯定会出现问题,无论共享的数据是否真的会出现竞争,它都会进行加锁,这会导致用户态到核心态转换、维护锁计数器和检查是否有被阻塞的线程需要被唤醒等开销。

基于冲突检测的乐观并发策略,:

  1. 不管风险,先进行操作,如果没有其他线程争用共享数据,那操作就直接成功了;
  2. 如果共享的数据的确被争用,产生了冲突,那再进行其他的补偿措施,
  3. 最常用的补偿措施是不断地重试,直到出现没有竞争的共享数据为止。
锁类型 描述 实现举例 优劣
悲观锁 悲观地认为别人也在修改,修改数据前加锁

Java中synchronizedReentrantLock

Mysql中行锁,表锁等,读锁,写锁

扫描二维码关注公众号,回复: 10908802 查看本文章
性能开销
乐观锁 乐观地认为别人不会修改,不会上锁

CAS、atomic类

版本号 update table set... where version=#{version} 类似于CAS

 

这种乐观并发策略的实现不再需要把线程阻塞挂起,因此这种同步操作被称为非阻塞同步(Non-Blocking Synchronization),使用这种措施的代码也常被称为无锁(Lock-Free)编程。

乐观锁适用于锁冲突少(读多写少)的场景,提高吞吐量,悲观锁适用于写多的场景。其实synchorinzed和ReentrantLock在实现上也借助CAS进行了优化,其实并不是直接阻塞线程的。
 

2.公平锁 VS 非公平锁

锁类型 描述 实现举例
公平锁

先到先得

ReentrantLock fairLock = new ReentrantLock(true);

FairSync

非公平锁

存在插队

ReentrantLock unfairLock = new ReentrantLock(false);

NonfairSync

为什么ReentrantLock的NonfairSync明明会将阻塞的线程排队啊?为什么是不公平的呢?

因为虽然在锁被占用的时候把其余竞争的线程插入了队列,且锁释放时会唤醒队头线程,但是如果在锁释放的时刻有新的线程竞争的话,不能确保队头线程能够获取锁,也有可能是新线程获取到锁。

在没有公平性需求的前提下尽量使用非公平锁,因为公平锁会带来性能开销。

3.独占锁 VS 共享锁

锁类型 描述 实现举例
独占锁

任何时候都只有一个线程能得到锁(互斥锁)

ReentrantReadWriteLock.WriteLock

共享锁 允许多个线程同时进行读操作

ReentrantReadWriteLock.ReadLock

4.可重入锁

当一个线程要获取一个被其他线程持有的独占锁时,该线程会被阻塞,

当一个线程再次获取它自己已经获取的锁时是否会被阻塞呢?如果不被阻塞,那么我们说该锁是可重入的,也就是只要该线程获取了该锁,那么可以多次地进入被该锁锁住的代码。

synchronized、ReentrantLock都是可以重入的,

实现

进入临界区:就是在发现锁被占用时会判断占用锁的线程id与当前线程ID,如果相同则可以进入,且计数值++;

离开(释放):计数器值-1。当计数器值为0时,锁里面的线程标示被重置为null,释放锁。

5. 自旋锁

一个线程在获取锁失败后,会被park(系统调用)挂起(需要切换到操作系统内核态);当锁释放时又需要去unpark(系统调用)唤醒之前被刮起的线程。

系统调用(从用户状态切换到内核状态)的开销很大,会影响并发性能。

自旋锁基于此优化

当前线程在获取锁失败,不马上阻塞自己,循环多次尝试获取(默认次数是10,可以使用-XX:PreBlockSpinsh参数设置该值),

很有可能在后面几次尝试中其他线程已经释放了锁。

如果尝试指定的次数后仍没有获取到锁则当前线程才会被阻塞挂起。

发布了132 篇原创文章 · 获赞 122 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/sarafina527/article/details/105362293