(3)Java并发编程基础篇

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/JavaMrZhang/article/details/87785650

乐观锁与悲观锁

乐观锁和悲观锁是在数据库中引入的名词,在java的并发包锁中也有类似的概念所以这边我们也有必要提及以下。

  • 悲观锁

悲观锁指在外界对数据进行修改的时候,它都持悲观的态度,认为数据都会被其他人进行修改,所以在获取、修改记录之前都会对记录进行加锁操作。下面看一个典型的例子:

public void updateAction(Integer id){
## 开启事务
line1 : TablePO tPO= getEntity("select * from table where id = #{id} for update;");
line2 : tPO.setXXX1("");
line3 : tPO.setXXX2("");
line4 : updateEntity("update set xxx1 = #{xxx1} AND xxx2=#{} where id = #{id}");
## 提交事务
}

当多个线程同时调用udpateAction时并同时传入相同的id,只有一个线程会成功执行line1代码,其他的线程将会阻塞,这个是因为同一时间内只能有一个线程能够获取对应记录的锁,直到这个线程成功的提交事务,释放锁之后其他线程才会被唤醒继续执行。

  • 乐观锁

乐观锁相对于悲观锁来说它认为一般情况下不会造成数据的冲突,只有在数据提交更新的时候,才会正式去检测数据是否冲突。具体来说是根据update 影响的行数来判定数据是否冲突。下面看一个典型的例子:

public void updateAction(Integer id){
## 开启事务
line1 : TablePO tPO= getEntity("select ID,VERSION,xxx from table where id = #{id}");
line2 : tPO.setXXX1("");
line3 : tPO.setXXX2("");
line4 : int affectRows = updateEntity("update set xxx1 = #{xxx1} AND version=version+1 where id = #{id} AND version=#{version}");
line5 : if(affectRows != 1 ){
		//.....
		}
## 提交事务

假设线程1 和 线程 2 在t1 时刻同时传入相同的ID,都会成功执行line1 的代码,获取相同的tPO(version=1),t2 时刻线程1 对 tPO进行属性赋值的操作并 根据条件id = #{id} AND version=#{version} 成功执行 line4 的更新操作,将version更改为2,t3 时刻 线程2 也对tPO进行属性的赋值 ,执行line4 时失败了,返回影响行数0,由于对应记录version值已经被线程1 更新为2了。这里就有点类似有CAS的操作了。

注意:

  1. t1 < t2 < t3
  2. 数据库中的update 语句本身就具有原子性,所以不会存在多个线程同时执行upadte 语句时出现数据不一致的问题。

公平锁与非公平锁

根据线程抢占锁的机制将锁分为了公平锁和非公平锁,公平锁指对于先请求锁的线程先获取锁,即先到先得,而非公锁锁没有这样的规定,先请求锁的线程不一定会先获取到锁。需要注意的是在没有公平性需求的情况下,尽量使用非公平锁,因为公平锁会带来一定的性能开销。

独占锁和共享锁

独占锁 任何时候只能有一个线程持有,它是一种悲观锁。共享锁 多个线程能同时持有,它是一种乐观锁。

什么是可重入锁

我们知道当一个线程要获取一个已经被其他线程持有的独占锁时,会被阻塞挂起。那么当一个线程要再次获取已经被它自身线程所持有的锁时会不会被阻塞挂起呢?如果不会那么就称它为可重入锁。

自旋锁

自旋锁指当线程要获取已经被其他线程已经持有的锁时,它并不阻塞挂起,而为进行多次重试获取该锁。自旋锁主要通过消耗CPU的时间来换取线程切换和调度的开销。

猜你喜欢

转载自blog.csdn.net/JavaMrZhang/article/details/87785650