image.png

什么是悲观锁 ? 什么是乐观锁

悲观锁:

站在mysql的角度分析:悲观锁就是比较悲观,当多个线程对同一行数据实现修改的时候,最后只有一个线程才能修改成功,只要谁能够对获取到行锁则其他线程时不能够对该数据做任何修改操作,且是 阻塞状态

站在java锁层面,如果没有获取到锁,则会阻塞等待,后期唤醒的锁的成本就会非常高。重新被我们的cpu从就绪调度到运行状态

乐观锁:

乐观锁比较乐观,通过预值或者版本号比较,如果不一致性的情况则通过循环控制修改,当前线程不会被阻塞,是乐观,效率比较高,但是乐观锁比较消耗cpu的资源。

乐观锁:获取锁---如果没有获取到锁,当前线程是不会阻塞等待,通过死循环控制

CAS有3个操作数,内存值V,旧的预期值E,要修改的新值N。

当且仅当预期值E和内存值V相同时,将内存值V修改为N,否则什么都不做。

Mysql层面如何实现乐观锁

在我们的表结构中会新增一个字段version(版本)字段

多个线程对同一行数据实现修改操作,提前查询当前最新的version版本号码,

作为update条件查询,如果当前version版本号码发生了变化,则查询不到该数据。表示如果修改数据失败则重试,又重新查询最新的版本实现update。

需要注意控制乐观锁循环的次数,避免cpu飙高的问题。mysql 的 innodb引擎中存在行锁的概念

Java层面实现乐观锁

CAS

  1. cas-无锁-juc并发包框架原子类-结合自旋

底层基于修改内存值V,E 旧的预期值

E==V则表示修改v=n;

  1. 基于cas 实现无锁机制原理;
  • 0表示没有线程获取该锁;
  • 1表示已经有线程获取到该锁;
  • Cas获取锁: Cas 修改该变量=0,1==cas修改成功的话则表示获取锁成功
  • Cas释放锁:将变量1修改为0表示释放锁成功

Java有哪些锁的分类

  • 悲观与乐观锁
  • 公平锁与非公平锁
  • 自旋锁/重入锁
  • 重量级锁/轻量级锁
  • 独占锁/共享锁

公平锁与非公平锁之间的区别

  • 公平锁:就是比较 公平 ,根据 请求锁的顺序排列 ,先来请求的就先获取锁,后来获取锁就最后获取到,采用队列存放类似于吃饭排队。
  • 非公平锁:不是根据根据请求的顺序排列, 通过争抢的方式获取锁 。非公平锁效率是公平锁效率要高,Synchronized是非公平锁

New ReentramtLock()(true)---公平锁

New ReentramtLock()(false)---非公平锁 AQS实现

公平锁底层是如何实现的

公平锁:就是比较公平,根据请求锁的顺序排列,先来请求的就先获取锁,后来获取锁就最后获取到,采用队列存放类似于吃饭排队。

队列--底层实现方式-数组或者链表实现

什么是锁的可重入性

在同一个线程中锁可以不断传递的,可以直接获取。

CAS(自旋锁)的优缺点

没有获取到锁的线程是不会阻塞的,通过循环控制一直不断的获取锁。

CAS: Compare and Swap,翻译成比较并交换。执行函数CAS (V,E,N)

CAS有3个操作数,内存值 V,旧的预期值 E,要修改的新值 N。当且仅当预期值E和内存值√相同时,将内存值V修改为N,否则什么都不做。

  1. Cas是通过硬件指令,保证原子性
  2. Java是通过unsafe jni技术

原子类:AtomicBoolean,AtomicInteger,AtomicLong等使用CAS 实现。

  • 优点:没有获取到锁的线程,会一直在用户态,不会阻塞,没有锁的线程会一直通过循环控制重试。
  • 缺点:通过死循环控制,消耗 cpu资源比较高,需要控制循次数,避免cpu飙高问题;

CAS本质的原理

getIntVolatile : 获取内存中当前该对象的最新值

CAS :比较在获取该值之后的间隙中是否值被改变,被改变则重新获取

CAS无锁机制原理

  1. 定义一个锁的状态;
  2. 状态状态值=0 则表示没有线程获取到该锁;
  3. 状态状态值=则表示有线程已经持有该锁;

实现细节

CAS 获取锁:

将该锁的状态从0改为1-----能够修改成功cas成功则表示获取锁成功如果获取锁失败--修改失败,则不会阻塞而是通过循环(自旋来控制重试)

CAS释放锁:

将该锁的状态从1改为0如果能够改成功cas成功则表示释放锁成功。

CAS 锁如何解决ABA的问题

Cas主要检查_内存值V与旧的预值值E是否一致,如果一致的情况下,则修改。这时候会存在ABA的问题:

如果将原来的值A,改为了B,B有改为了A发现没有发生变化,实际上已经发生了变化,所以存在ABA问题。

解决办法:通过版本号码,对每个变量更新的版本号码做+1

解决aba问题是否大:概念产生冲突,但是不影响结果,换一种方式通过版本号码方式。

如何利用原子类手写CAS无锁

package com.example.test.study;

import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.IntStream;

/**
* @author Wcy
* @Date 2022/10/6 0:39
*/
public class AtomicTryLock {

    private AtomicLong cas = new AtomicLong(0);

    private Thread lockCurrentThread;
    /**
* 获取锁
* 锁是有状态:0表示未锁定,1表示锁定
*/
    private boolean tryLock(){
        boolean res = cas.compareAndSet(0, 1);
        if(res){
            lockCurrentThread = Thread.currentThread();
        }
        return res;
    }

    /**
* 释放锁
*/
    private boolean unlock(){
        if (lockCurrentThread != Thread.currentThread()){
            return false;
        }
        return cas.compareAndSet(1,0);
    }

    public static void main(String[] args) {
        AtomicTryLock atomicTryLock = new AtomicTryLock();
        IntStream.range(1,10).forEach(i->{
            new Thread(()->{
                try {
                    boolean res =  atomicTryLock.tryLock();
                    if(res) {
                        atomicTryLock.lockCurrentThread = Thread.currentThread();
                        System.out.println(Thread.currentThread().getName() + "获取锁成功");
                    }else{
                        System.out.println(Thread.currentThread().getName() + "获取锁失败");
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }finally {
                    if(atomicTryLock!=null){
                        atomicTryLock.unlock();
                    }
                }
            }).start();
        });
    }
}

最后修改:2022 年 10 月 06 日
如果觉得我的文章对你有用,请随意赞赏