CAS + 自旋 锁底层

这篇具有很好参考价值的文章主要介绍了CAS + 自旋 锁底层。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

多线程安全问题

  1. 为什么会出现多线程安全问题?
    在多线程并发下, 假设有 A,B 两个线程同时操作 count = 0 这个公共变量, 在A线程中count++, 在B线程中count++, 正常来说结果应该是 count = 2, 可是同时在A, B两个线程中拿到 count = 0 , 并且都执行count++赋值, 结果就变成了 count = 1
  2. 解决办法?
    • 原子操作类: AtomicLong, AtomicInteger, LongAdder(java8以上并发量大的情况下推荐)
    • 锁: Lock: ReentrantLock , synchronized

1 Volatile关键字

解决多线程内存不可见问题, 对于一写多读可以解决变量同步问题, 多些则无法解决线程安全问题.

2 CAS + 原子操作类

2.1 CAS是什么?

CAS是英文单词Compare And Swap的缩写,翻译过来就是`比较并替换`。
CAS性能优势分析:
	主频 1GHz 的cpu: 2-3纳秒
	一次上下文切换耗时2-8微妙
	
	1秒 = 1000毫秒
	1毫秒 = 1000微秒
	1微秒 = 1000纳秒	

2.2 AtomicLong实现

// 创建一个对象
AtomicLong atomicLong = new AtomicLong();
// 递增并获取最新的
long l = atomicLong.incrementAndGet();


public class AtomicLong extends Number implements java.io.Serializable {
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;
    static {
        try {
	        // 获取一块内存地址的偏移量, 在内存地址的编号
            valueOffset = unsafe.objectFieldOffset(AtomicLong.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

	// 调用的是以下这个方法
	public final long incrementAndGet() {
		// 自身, 偏移量, 1L
	   return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
	}
}

// AtomicLong, 偏移量, 1L
public final long getAndAddLong(Object var1, long var2, long var4) {
    long var6;
    do {
	    // 拿到内存中的最新值
        var6 = this.getLongVolatile(var1, var2);
       // 计算 var1(AtomicLong)在内存 var2(valueOffset)中的结果 是否等于old(var6)值
       //  == 返回var6
       // !== 赋值var6+var4, 自旋 重新获取var6
    } while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4)); // 这个是调用的c++然后使用汇编进行处理的, C++在多核cpu中也进行了锁处理
    return var6;
}

2.3 ABA问题

  1. ABA问题是什么?

    ABA问题是三种状态, 在第一次获取时对象的属性值为A, 这时被其他线程进行修改成B, 又被修改成A, 在第二次判断比较的时候看到还是A, 所以就进行了计算, 可是实际上已经被修改过了, 在int, 等基础类型中,没有问题, 可是在对象Object中会出现问题.

    // 创建string类型的CAS对象
    static AtomicReference<String> accessString  = new AtomicReference<>("A");
    public static void main(String[] args) throws InterruptedException {
       new Thread(()->{
           // 是A的话更新成B
           boolean b1 = accessString.compareAndSet("A", "B");
           // 是B的话更新成A
           boolean b2 = accessString.compareAndSet("B", "A");
           System.err.println("ABA问题 = "+(b1&b2));
       }).start();
       
       Thread.sleep(1000);
       
       new Thread(()->{
           boolean b3 = accessString.compareAndSet("A","C");
           System.err.println("CAS结果 = " + b3);
       }).start();
    }
    

    结果:
    ABA问题 = true
    CAS结果 = true

  2. 解决方法

    使用乐观锁机制, (时间戳, 版本号) + 自旋

    // 创建包含记录的对象
    static AtomicStampedReference<String> accessStampString  = new AtomicStampedReference<>("A", 0);
    public static void main(String[] args) throws InterruptedException {
        int stamp = accessStampString.getStamp();
        new Thread(()->{
            // 是A的话更新成B, 判断时间戳是否是之前的, 是的话+1
            boolean b1 = accessStampString.compareAndSet("A", "B", accessStampString.getStamp(), accessStampString.getStamp()+1);
            // 是B的话更新成A, 判断时间戳是否是之前的, 是的话+1
            boolean b2 = accessStampString.compareAndSet("B", "A", accessStampString.getStamp(), accessStampString.getStamp()+1);
            System.err.println("ABA问题 = "+(b1&b2));
        }).start();
        Thread.sleep(1000);
    
        new Thread(()->{
            boolean b3 = accessStampString.compareAndSet("A","C", stamp, stamp+1);
            System.err.println("CAS结果 = " + b3);
        }).start();
    }
    
    

    结果:
    ABA问题 = true
    CAS结果 = false

3 AtomicLong 和 LongAdder

AtomicLong(1.8之前) 和 LongAdder(1.8及以后) 都能解决多线程问题, 那么我们用哪个?

  1. AtomicLong 底层中, 是对一个base对象进行操作, 多线程中, 第一个写入, 第二个第三个写入失败则CAS重试,导致CPU开销高
  2. LongAdder 底层中, 如果是单线程,则跟AtomicLong没有太大区别, 对base值进行计算, 如果是多线程环境则实行分片机制, 多线程进行写入时, 默认会创建两个cell进行计算, 每个cell分一些线程进入, 如果线程写入失败, 则创建更多的cell, 最后使用sum() = base + cell[0] + cell[1]+... 方法进行计算

在线程数量少的时候, 使用AtomicLong的时间更快, 在线程量多的话, 使用LongAdder更快, 线程数量越多,两个的差别越大

CAS + 自旋 锁底层,java,多线程,java

4 ReentrantLock 可重入锁 与 Synchronized

    static Lock lock = new ReentrantLock();
    int count = 0;
    public static void main(String[] args) {
        lock.lock();  //加锁
        try {
            // 业务代码
			count++;
        }finally {
            lock.unlock(); // 释放锁
        }
    }

4.1 手写MyLock

import org.jetbrains.annotations.NotNull;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;

/**
 * @author xyy
 * @DateTime 2023/7/3 13:20
 * @ClassName MyLock
 */
public class MyLock implements Lock {
    /**
     * 实现CAS比较对象
     * 锁的持有者
     */
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    /**
     * 阻塞队列
      */
    LinkedBlockingQueue<Thread> linkedBlockingQueue = new LinkedBlockingQueue<>();
    @Override
    public void lock() {
        // 如果里面是空则成功写入
        while (!atomicReference.compareAndSet(null, Thread.currentThread())){
            // 有锁被占用, 写入阻塞队列
            linkedBlockingQueue.add(Thread.currentThread());
            //让当前线程阻塞, 相当于打上断点!!!打断点!!!调试的时候的断点!!!
            // 在lock时进行停车阻塞, 在unlock时继续执行这里的代码!!!
            LockSupport.park();
            // 删除阻塞队列中的本线程, 重新竞争lock, 必须要删除!!!否则容易内存泄漏
            linkedBlockingQueue.remove(Thread.currentThread());
        }
    }

    @Override
    public void unlock() {
        // 只有当前锁的线程才能释放锁(CAS)
        if(atomicReference.compareAndSet(Thread.currentThread(), null)){
            // 将阻塞队列中的数据进行开放
            for (Thread thread : linkedBlockingQueue) {
                // unpark方法不一定能唤醒, 所以从这里删除阻塞队列不行
                LockSupport.unpark(thread);
            }
        }
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        return false;
    }

    @Override
    public boolean tryLock(long time, @NotNull TimeUnit unit) throws InterruptedException {
        return false;
    }

    @NotNull
    @Override
    public Condition newCondition() {
        return null;
    }
}

4.2 synchronized

在 jdk1.6之前 synchronized是一把重量级锁, 跟 4.1 一样的,(在线程的上下文切换中非常耗时, 线程A上文记录, 恢复线程B的上文继续执行B的下文)
CAS + 自旋 锁底层,java,多线程,java

6 ConcurrentHashMap 并发

ConcurrentHashMap 其实底层是初始16个HashMap, 在扩容时乘以2, 保证数组长度是2的幂次方, 减少hash冲突,数组下标计算方式(n-1) & hash, 而2的幂次方-1的二级制数都是1, hash冲突可能性最小.

在多线程中, put会进行hash计算, 然后加锁写入相应的hashMap中, 因为初始有16个hashmap, 则可以同时处理多线程的问题文章来源地址https://www.toymoban.com/news/detail-518684.html

到了这里,关于CAS + 自旋 锁底层的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Java多线程进阶】CAS机制

    前言 CAS指的是Compare-And-Swap(比较与交换),它是一种多线程同步的技术,常用于实现无锁算法,从而提高多线程程序的性能和扩展性。本篇文章具体讲解如何使用 CAS 的机制以及 CAS 机制带来的问题。 目录 1. 什么是CAS? 2. CAS的应用 2.1 实现原子类 2.2 实现自旋锁 3. CAS的ABA问

    2024年02月10日
    浏览(39)
  • Java多线程系列——CAS机制

    在并发编程的世界里,线程安全是个不得不面对的问题,而CAS(Compare-And-Swap,比较并交换)正是保障并发安全中一种非常关键的机制。本文将深入剖析Java多线程环境下的CAS机制,包括其工作原理、实现方式、面临的问题以及相关的优化策略,力求为读者带来全面的了解。  

    2024年02月22日
    浏览(46)
  • 深入浅出Java多线程(十):CAS

    大家好,我是你们的老伙计秀才!今天带来的是[深入浅出Java多线程]系列的第十篇内容:CAS。大家觉得有用请点赞,喜欢请关注!秀才在此谢过大家了!!! 在多线程编程中,对共享资源的安全访问和同步控制是至关重要的。传统的锁机制,如synchronized和ReentrantLock等

    2024年03月11日
    浏览(54)
  • Java多线程(3)---锁策略、CAS和JUC

    目录 前言 一.锁策略 1.1乐观锁和悲观锁 ⭐ 两者的概念 ⭐实现方法 1.2读写锁  ⭐概念 ⭐实现方法 1.3重量级锁和轻量级锁 1.4自旋锁和挂起等待锁 ⭐概念 ⭐代码实现 1.5公平锁和非公平锁 1.6可重入锁和不可重入锁 二.CAS 2.1为什么需要CAS 2.2CAS是什么 ⭐CAS的介绍 ⭐CAS工作原理

    2024年02月13日
    浏览(71)
  • Java进阶(ConcurrentHashMap)——面试时ConcurrentHashMap常见问题解读 & 结合源码分析 & 多线程CAS比较并交换 初识

    List、Set、HashMap作为Java中常用的集合,需要深入认识其原理和特性。 本篇博客介绍常见的关于Java中线程安全的ConcurrentHashMap集合的面试问题,结合源码分析题目背后的知识点。 关于List的博客文章如下: Java进阶(List)——面试时List常见问题解读 结合源码分析 关于的Set的博

    2024年02月06日
    浏览(61)
  • 【Linux】线程池 | 自旋锁 | 读写锁

    线程池是一种 线程使用模式 。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度

    2024年02月07日
    浏览(35)
  • JavaEE 初阶篇-深入了解 CAS 机制与12种锁的特征(如乐观锁和悲观锁、轻量级锁与重量级锁、自旋锁与挂起等待锁、可重入锁与不可重入锁等等)

    🔥博客主页: 【 小扳_-CSDN博客】 ❤感谢大家点赞👍收藏⭐评论✍ 文章目录         1.0 乐观锁与悲观锁概述         1.1 悲观锁(Pessimistic Locking)         1.2 乐观锁(Optimistic Locking)         1.3 区别与适用场景         2.0 轻量级锁与重量级锁概述         2.1 真正加

    2024年04月16日
    浏览(35)
  • java八股文面试[多线程]——Synchronized的底层实现原理

    笔试:画出Synchronized 线程状态流转 实现原理图 synchronized解决的是多个线程之间访问资源的同步性,synchronized 翻译为中文的意思是 同步 ,也称之为”同步锁“。 synchronized的作用是保证在 同一时刻 , 被修饰的代码块或方法只会有一个线程执行,以达到保证并发安全的

    2024年02月10日
    浏览(48)
  • java八股文面试[多线程]——ThreadLocal底层原理和使用场景

    源码分析: ThreadLocal中定义了ThreadLocalMap静态内部类,该内部类中又定义了Entry内部类。 ThreadLocalMap定了 Entry数组。 Set方法: Get方法: Thread中定义了两个ThreaLocalMap成员变量: Spring使用ThreadLocal解决线程安全问题  我们知道在一般情况下,只有 无状态的Bean 才可以在多线程环

    2024年02月10日
    浏览(52)
  • 一文搞定Linux线程间通讯 / 线程同步方式-互斥锁、读写锁、自旋锁、信号量、条件变量、信号等等

    目录 线程间通讯 / 线程同步方式 锁机制 互斥锁(Mutex) 读写锁(rwlock) 自旋锁(spin) 信号量机制(Semaphore) 条件变量机制 信号(Signal) 线程间通讯 / 线程同步方式 p.s 以下有很多段落是直接引用,没有使用 markdown 的 “引用” 格式,出处均已放出。 参考 / 引用: 100as

    2024年02月10日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包