线程悲观锁乐观锁定义,java的乐观锁和悲观锁

  线程悲观锁乐观锁定义,java的乐观锁和悲观锁

  00-1010 1.悲观锁2的问题。通过CAS 3实现乐观锁。不可重入旋转锁4。可重入自旋锁问题:综述

  1.对乐观锁和悲观锁的理解,如何实现,如何实现?

  2.什么是乐观锁和悲观锁?

  3.乐观锁定能否重入?

  00-1010独占锁其实就是悲观锁,java的同步锁就是悲观锁。悲观锁可以确保无论哪个线程持有锁,都可以独占访问临界区。悲观锁的逻辑虽然很简单,但是问题很多。

  悲观锁总是假设最坏的情况会发生。每次线程读取数据,都会被锁定。这样,其他线程在读取数据时会被阻塞,直到它获得锁。传统的关系数据库使用许多悲观锁,如行锁、表锁、读锁和写锁。

  悲观锁机制存在以下问题:

  (1)在多线程竞争下,加锁和释放锁会导致更多的上下文切换和调度延迟,从而产生性能问题。

  (2)当一个线程持有锁时,会导致所有其他抢占锁的线程挂起。

  (3)如果高优先级的线程等待低优先级的线程释放锁,会造成线程的优先级倒置,从而造成性能风险。

  解决这些问题的有效方法是使用乐观锁而不是悲观锁。同样,在数据库操作中,数据的版本号更新和JUC包的原子类都使用乐观锁来提高性能。

  00-1010乐观锁的操作主要包括两步:(1)第一步:冲突检测。(2)第二步:更新数据。

  乐观锁的典型例子是CAS原子操作,JUC强大的高并发性就是基于CAS原子的。CAS操作包含三个操作数:要操作的内存位置(V)、要比较的预期原始值(A)和要写入的新值(B)。如果存储单元V的值与预期的原始值A相匹配,处理器将自动将该存储单元的值更新为新值B;否则,处理器什么也不做。

  CAS操作可以非常清晰地分为两个步骤:

  (1)检查位置v的值是否为a.

  (2)如果是,则将位置V更新为B的值;否则,不要更改位置。

  CAS操作的两个步骤其实和乐观锁操作是一样的,冲突检测后更新数据。

  乐观锁是一种思想,CAS是这种思想的实现。事实上,如果需要完成数据的最终更新,只执行一次CAS操作是不够的。一般情况下,你需要执行spin操作,即反复重试CAS操作,直到成功,这也叫CAS spin。通过CAS spin,在不使用锁的情况下实现多个线程之间的变量同步,即在不阻塞线程的情况下实现变量同步,称为“非阻塞同步”或“无锁同步”。使用基于CAS spin的乐观锁进行同步控制是无锁编程的一种实践。

  00-1010自旋锁的基本意思是:当一个线程正在获取锁的时候,如果锁已经被其他线程获取了,调用者会一直在那里检查锁是否已经被释放,直到获取了锁才会退出循环。

  CAS自旋锁的实现原理为:

  锁抓取线程不断执行CAS spin操作来更新锁的所有者。如果更新成功,意味着抢锁成功,抢锁方法退出。如果锁已被其他线程获取(即所有者是另一个线程),调用方将在那里循环所有者的CAS更新操作,直到成功,并且不会退出循环。

  Public类pinlock实现lock {//当前锁的所有者,private atomic reference threadowner=new atomic reference();@ Override public void lock(){ Thread t=Thread . currentthread();//spin while(owner.compare andset(null,t)){//放弃CPU的时间片thread . yield();} } @ Override public void unlock(){ Thread t=Thread . currentthread();//如果(t==owner.get()){ //将所有者设置为空,则只有所有者可以获取锁。这里不需要使用compareAndSet,因为owner.set(null)已经通过owner进行了线程检查;} }//省略其他代码.}上面的SpinLock不支持重入,也就是说,当线程第一次获得锁时,在锁被释放之前,如果再次

  重新获取该锁,第二次将不能成功获取到,因为自旋后CAS会失败。

  

 

  

4. 可重入的自旋锁

为了实现可重入锁,这里引入一个计数器,用来记录一个线程获取锁的次数。一个简单的可重入的自旋锁的代码大致如下:

 

  

public class ReentrantSpinLock implements Lock { // 当前锁的拥有者,使用Thread作为同步状态 AtomicReference<Thread> owner = new AtomicReference<>(); // 记录一个线程重复获取锁的次数 private int count = 0; // 抢占锁 @Override public void lock() { Thread t = Thread.currentThread(); // 如果时冲入,增加重入次数后,返回 if(t==owner.get()){ count++; return; } // 自旋 while (owner.compareAndSet(null,t)){ Thread.yield(); } } @Override public void unlock() { Thread t = Thread.currentThread(); // 只有拥有者才能释放锁 if(t==owner.get()){ // 如果重入次数大于0,减少重入次数后返回 if(count>0){ count--; }else{ // 设置拥有者为null owner.set(null); } } } // 省略其他代码...}

自旋锁的特点:线程获取锁的时候,如果锁被其他线程持有,当前线程将循环等待,直到获取到锁。线程抢锁期间状态不会改变,一直是运行状态(RUNNABLE),在操作系统层面线程处于用户态。

 

  自旋锁的问题:在争用激烈的场景下,如果某个线程持有锁的时间太长,就会导致其他空自旋的线程耗尽CPU资源。另外,如果大量的线程进行空自旋,还可能导致硬件层面的总线风暴。

  在争用激烈的场景下,Java轻量级锁会快速膨胀为重量级锁,其本质上一是为了减少CAS空自旋,二是为了避免同一时间大量CAS操作所导致的总线风暴。那么,JUC基于CAS实现的轻量级锁如何避免总线风暴呢?答案是:使用队列对抢锁线性进行排队,最大程度上减少了CAS操作数量。

  

 

  

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注盛行IT的更多内容!

 

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: