Java 多线程与锁策略的深入探讨

news2024/10/4 15:22:22

在 Java 的多线程编程中,锁策略、CAS(Compare and Swap)机制以及 synchronized 的优化过程是非常重要的概念。本文将对这些知识点进行总结和讲解,并加入一些代码示例以帮助理解。

一、锁策略


1. 悲观锁与乐观锁

  • 悲观锁:总是假设最坏的情况,每次访问数据时都加锁,确保数据安全。

    • 示例
public class PessimisticLockExample {  
    private final Object lock = new Object();  
    private int data;  

    public void updateData(int newData) {  
        synchronized (lock) {  
            data = newData; // 加锁,确保数据安全  
        }  
    }  
}

乐观锁:假设不会发生冲突,只有在提交更新时才会检查是否有其他线程修改了数据。

  • 示例
public class OptimisticLockExample {  
    private int data;  

    public boolean updateData(int expectedValue, int newValue) {  
        if (data == expectedValue) {  
            data = newValue; // 直接更新数据  
            return true;  
        }  
        return false; // 更新失败  
    }  
}  

 Synchronized 初始使⽤乐观锁策略. 当发现锁竞争⽐较频繁的时候, 就会⾃动切换成悲观锁策略.

2. 重量级锁与轻量级锁

锁的核⼼特性 "原⼦性", 这样的机制追根溯源是 CPU 这样的硬件设备提供的.

  1. CPU 提供了 "原⼦操作指令".
  2.  操作系统基于 CPU 的原⼦指令, 实现了 mutex 互斥锁.
  3.  JVM 基于操作系统提供的互斥锁, 实现了 synchronized 和 ReentrantLock 等关键字和类.
  • 重量级锁:依赖于操作系统提供的互斥锁,涉及到用户态和内核态的切换,成本较高。
  • 轻量级锁:尽量在用户态完成加锁操作,只有在必要时才使用互斥锁。

synchronized 开始是⼀个轻量级锁. 如果锁冲突⽐较严重, 就会变成重量级锁.

3. 自旋锁

自旋锁是一种轻量级锁,线程在获取锁失败后不会进入阻塞状态,而是持续尝试获取锁,直到成功

  • 示例
public class SpinLock {  
    private volatile Thread owner = null;  

    public void lock() {  
        while (!compareAndSetOwner(null, Thread.currentThread())) {  
            // 自旋等待  
        }  
    }  

    public void unlock() {  
        owner = null;  
    }  

    private boolean compareAndSetOwner(Thread expected, Thread newOwner) {  
        if (owner == expected) {  
            owner = newOwner;  
            return true;  
        }  
        return false;  
    }  
}

 如果获取锁失败, ⽴即再尝试获取锁, ⽆限循环, 直到获取到锁为⽌. 第⼀次获取锁失败, 第⼆次的尝试会 在极短的时间内到来.

⼀旦锁被其他线程释放, 就能第⼀时间获取到锁.

⾃旋锁是⼀种典型的 轻量级锁 的实现⽅式.

优点: 没有放弃 CPU, 不涉及线程阻塞和调度, ⼀旦锁被释放, 就能第⼀时间获取到锁.

缺点: 如果锁被其他线程持有的时间⽐较久, 那么就会持续的消耗 CPU 资源. (⽽挂起等待的时候是不 消耗 CPU 的).

synchronized 中的轻量级锁策略⼤概率就是通过⾃旋锁的⽅式实现的.

 4. 公平锁与非公平锁

  • 公平锁:遵循“先来后到”的原则,确保按照请求顺序获取锁。
  • 非公平锁:不保证顺序,可能导致后来的线程优先获取锁。

操作系统内部的线程调度就可以视为是随机的. 如果不做任何额外的限制, 锁就是⾮公平锁. 如果要 想实现公平锁, 就需要依赖额外的数据结构, 来记录线程们的先后顺序。

synchronized 是⾮公平锁.

5. 可重入锁

可重入锁指的是同一线程可以多次获取同一把锁而不会导致死锁。

Java⾥只要以Reentrant开头命名的锁都是可重⼊锁,⽽且JDK提供的所有现成的Lock实现类,包括 synchronized关键字锁都是可重⼊的。

⽽ Linux 系统提供的 mutex 是不可重⼊锁.

  • 示例
import java.util.concurrent.locks.ReentrantLock;  

public class ReentrantLockExample {  
    private final ReentrantLock lock = new ReentrantLock();  

    public void method() {  
        lock.lock();  
        try {  
            // 业务逻辑  
            method(); // 递归调用  
        } finally {  
            lock.unlock();  
        }  
    }  
}  

synchronized 是可重⼊锁

6. 读写锁

读写锁允许多个线程同时读取数据,但写操作是互斥的。

  • 示例:
import java.util.concurrent.locks.ReentrantReadWriteLock;  

public class ReadWriteLockExample {  
    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();  
    private final ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();  
    private final ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();  
    private int sharedData = 0;  

    public void readData() {  
        readLock.lock();  
        try {  
            // 读取共享数据  
            System.out.println("Reading data: " + sharedData);  
        } finally {  
            readLock.unlock();  
        }  
    }  

    public void writeData(int data) {  
        writeLock.lock();  
        try {  
            // 写入共享数据  
            sharedData = data;  
            System.out.println("Writing data: " + sharedData);  
        } finally {  
            writeLock.unlock();  
        }  
    }  
}

二、CAS(Compare and Swap)机制


CAS: 全称Compare and swap,字⾯意思:”⽐较并交换“,⼀个 CAS 涉及到以下操作:

  1. ⽐较 A 与 V 是否相等。(⽐较)
  2. 如果⽐较相等,将 B 写⼊ V。(交换)
  3. 返回操作是否成功。

CAS 是一种乐观锁的实现方式,涉及三个操作:

  1. 比较内存中的值与预期值是否相等。
  2. 如果相等,则将新值写入内存。
  3. 返回操作是否成功。
  • 示例:
public class CASExample {  
    private volatile int value;  

    public boolean compareAndSet(int expectedValue, int newValue) {  
        if (value == expectedValue) {  
            value = newValue; // 交换  
            return true;  
        }  
        return false; // 操作失败  
    }  
}

针对不同的操作系统,JVM ⽤到了不同的 CAS 实现原理,简单来讲:

  • ava 的 CAS 利⽤的的是 unsafe 这个类提供的 CAS 操作;
  • unsafe 的 CAS 依赖了的是 jvm 针对不同的操作系统实现的 Atomic::cmpxchg;
  • Atomic::cmpxchg 的实现使⽤了汇编的 CAS 操作,并使⽤ cpu 硬件提供的 lock 机制保证其原⼦ 性。

1. 原子变量操作

Java提供了多种原子类(如AtomicInteger、AtomicLong、AtomicBoolean等),它们都使用CAS来实现原子更新操作。这些类在不使用锁的情况下提供了一种无锁的线程安全机制。

  • 示例
AtomicInteger atomicInteger = new AtomicInteger(0);  

public void increment() {  
    atomicInteger.incrementAndGet(); // 使用CAS实现原子性  
}

2. 自旋锁

自旋锁通过CAS循环不断尝试获取锁。与传统锁不同,自旋锁不会使线程进入阻塞状态。

  • 示例
public class SpinLock {  
    private AtomicReference<Thread> owner = new AtomicReference<>();  
    
    public void lock() {  
        Thread current = Thread.currentThread();  
        while (!owner.compareAndSet(null, current)) {  
            // 自旋等待  
        }  
    }  
    
    public void unlock() {  
        owner.set(null);  
    }  
}

3. ABA问题解决方案

CAS操作中的ABA问题是指:在一次CAS操作期间,一个变量可能被其它线程改变为另一个值,然后又恢复为原来的值。

为了解决这个问题,Java提供了AtomicStampedReference和 AtomicMarkableReference它们通过版本号(或标记)来避免ABA问题。

  • 示例:
import java.util.concurrent.atomic.AtomicStampedReference;  

public class AtomicStampedReferenceExample {  
    // 创建一个AtomicStampedReference对象,初始值为100,初始版本号为0  
    private AtomicStampedReference<Integer> stampedRef = new AtomicStampedReference<>(100, 0);  

    // 更新操作  
    public void update() {  
        // 用于存储当前版本号的数组  
        int[] stampHolder = new int[1];  

        // 获取当前值,并通过stampHolder获取当前的版本号  
        Integer value = stampedRef.get(stampHolder); // value为当前值,stampHolder[0]为当前版本号  

        // 计算新的版本号  
        int newStamp = stampHolder[0] + 1; // 新版本号为当前版本号加1  

        // 尝试使用CAS更新值和版本号  
        // 如果当前值和版本号分别等于value和stampHolder[0],则将值更新为value + 1,版本号更新为newStamp  
        boolean updated = stampedRef.compareAndSet(value, value + 1, stampHolder[0], newStamp);  

        // 结果:更新成功返回true,更新失败返回false  
        if (updated) {  
            System.out.println("Update successful: Value is " + stampedRef.getReference() + " with stamp " + stampedRef.getStamp());  
        } else {  
            System.out.println("Update failed");  
        }  
    }  

    public static void main(String[] args) {  
        AtomicStampedReferenceExample example = new AtomicStampedReferenceExample();  
        example.update(); // 对stampedRef进行更新操作  
    }  
}

CAS为多线程编程提供了高效无锁的同步机制,广泛应用于需要多线程安全且高性能的场合。它通过乐观并发控制,允许多个线程同时操作数据,大大提高了系统的可伸缩性和响应速度。

3. synchronized的优化过程


synchronized关键字是Java中用于实现同步的基本机制之一。在JDK 1.8中,synchronized经历了一系列的优化,以提高其性能和效率。

1. 乐观锁与悲观锁

  • 乐观锁synchronized在初始阶段采用乐观锁的策略,假设不会发生锁竞争,因此不立即加锁。
  • 悲观锁:如果锁竞争频繁,synchronized会转换为悲观锁,确保线程安全。

2. 轻量级锁与重量级锁

  • 轻量级锁:在锁竞争不激烈的情况下,synchronized使用轻量级锁。轻量级锁通过CAS操作实现,避免了线程阻塞。
  • 重量级锁:如果锁被持有的时间较长或竞争激烈,轻量级锁会膨胀为重量级锁,使用操作系统的互斥量来实现线程阻塞和唤醒。

3. 自旋锁

  • 自旋锁:在实现轻量级锁时,synchronized大概率会使用自旋锁策略。自旋锁让线程在短时间内反复尝试获取锁,而不是立即进入阻塞状态,从而减少线程上下文切换的开销。

4. 不公平锁

  • 不公平锁synchronized是一种不公平锁,意味着线程获取锁的顺序不一定按照请求的顺序进行。这种策略可以提高吞吐量,但可能导致某些线程长期得不到锁。

5. 可重入锁

  • 可重入锁synchronized是可重入锁,同一线程可以多次获取同一把锁而不会导致死锁。这是通过在锁中记录持有锁的线程和计数器来实现的。

6. 锁消除

  • 锁消除:编译器和JVM会判断某些锁是否可以消除。如果在单线程环境中使用了synchronized,编译器可能会优化掉这些不必要的锁。

7. 锁粗化

  • 锁粗化:如果在一段逻辑中多次加锁和解锁,编译器和JVM会自动进行锁粗化,将多个锁操作合并为一个较大的锁操作,以减少频繁的加锁和解锁开销。

通过这些优化,synchronized在JDK 1.8中变得更加高效,能够在保证线程安全的同时,尽量减少锁带来的性能损耗。这些优化使得synchronized在许多情况下成为一个性能良好的同步机制。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2188254.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

五子棋双人对战项目(6)——对战模块(解读代码)

目录 一、约定前后端交互接口的参数 1、房间准备就绪 &#xff08;1&#xff09;配置 websocket 连接路径 &#xff08;2&#xff09;构造 游戏就绪 的 响应对象 2、“落子” 的请求和响应 &#xff08;1&#xff09;“落子” 请求对象 &#xff08;2&#xff09;“落子…

如何使用ssm实现基于java的实验室设备管理系统

TOC ssm768基于java的实验室设备管理系统jsp 绪论 1.1研究背景与意义 1.1.1研究背景 近年来&#xff0c;第三产业发展非常迅速&#xff0c;诸如计算机服务、旅游、娱乐、体育等服务行业&#xff0c;对整个社会的经济建设起到了极大地促进作用&#xff0c;这一点是毋庸置疑…

SpringBoot与舞蹈艺术:古典舞在线交流平台开发记

第二章 相关技术介绍 2.1Java技术 Java是一种非常常用的编程语言&#xff0c;在全球编程语言排行版上总是前三。在方兴未艾的计算机技术发展历程中&#xff0c;Java的身影无处不在&#xff0c;并且拥有旺盛的生命力。Java的跨平台能力十分强大&#xff0c;只需一次编译&#xf…

【基础算法总结】链表篇

目录 一&#xff0c; 链表常用技巧和操作总结二&#xff0c;算法原理和代码实现2.两数相加24.两两交换链表中的节点143.重排链表23.合并k个升序链表25.k个一组翻转链表 三&#xff0c;算法总结 一&#xff0c; 链表常用技巧和操作总结 有关链表的算法题也是一类常见并且经典的题…

案例-猜数字游戏

文章目录 效果展示初始画面演示视频 代码区 效果展示 初始画面 演示视频 猜数字游戏 代码区 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width,…

【题解】【动态规划,最长上升子序列LIS】—— [CSP-J 2022] 上升点列

【题解】【动态规划&#xff0c;最长上升子序列LIS】—— [CSP-J 2022] 上升点列 [CSP-J 2022] 上升点列题目描述输入格式输出格式输入输出样例输入 #1输出 #1输入 #2输出 #2 提示 1.题意解析2.AC代码 [CSP-J 2022] 上升点列 通往洛谷的传送门 题目描述 在一个二维平面内&am…

GAMES101(19节,相机)

相机 synthesis合成成像&#xff1a;比如光栅化&#xff0c;光线追踪&#xff0c;相机是capture捕捉成像&#xff0c; 但是在合成渲染时&#xff0c;有时也会模拟捕捉成像方式&#xff08;包括一些技术 动态模糊 / 景深等&#xff09;&#xff0c;这时会有涉及很多专有名词&a…

确保接口安全:六大方案有效解决幂等性问题

文章目录 六大方案解决接口幂等问题什么是接口幂等&#xff1f;天然幂等不做幂等会怎么样&#xff1f; 解决方案1&#xff09;insert前先select2&#xff09;使用唯一索引3&#xff09;去重表加悲观锁4&#xff09;加乐观锁之版本号机制5&#xff09;使用 Redisson 分布式锁6&a…

银河麒麟系统内存清理

银河麒麟系统内存清理 1、操作步骤2、注意事项 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 当银河麒麟系统运行较长时间&#xff0c;内存中的缓存可能会积累过多&#xff0c;影响系统性能。此时&#xff0c;你可以通过简单的命令来清理这…

问:LINUXWINDOWS线程CPU时间如何排序?

Linux 在Linux上&#xff0c;你可以使用ps命令结合sort命令来查看和排序进程或线程的CPU使用时间。 查看进程的CPU使用时间并按时间排序 使用ps命令的-o选项可以自定义输出格式&#xff0c;-e选项表示显示所有进程&#xff0c;--sort选项用于排序。 ps -e -o pid,tid,comm,…

排序大全(干货)

目录 1. 插入排序步骤&#xff1a; 2.选择排序思路&#xff1a;每次从待排序列中选出一个最小值&#xff0c;然后放在序列的起始位置&#xff0c;直到全部待排数据排完即可。实际上&#xff0c;我们可以一趟选出两个值&#xff0c;一个最大值一个最小值&#xff0c;然后将其放…

【springboot】整合LoadBalancer

目录 问题产生背景解决方案&#xff1a;实现LoadBalancer1. 添加依赖2. 配置文件3. 使用LoadBalancer4. 使用 RestTemplate 进行服务调用5. 测试 问题产生背景 以下是一个购物车项目&#xff0c;通过调用外部接口获取商品信息&#xff0c;并添加到购物车中&#xff0c;这段代码…

如何使用ssm实现中学生课后服务的信息管理与推荐+vue

TOC ssm766中学生课后服务的信息管理与推荐vue 第一章 绪论 1.1 选题背景 目前整个社会发展的速度&#xff0c;严重依赖于互联网&#xff0c;如果没有了互联网的存在&#xff0c;市场可能会一蹶不振&#xff0c;严重影响经济的发展水平&#xff0c;影响人们的生活质量。计算…

查缺补漏----I/O中断处理过程

中断优先级包括响应优先级和处理优先级&#xff0c;响应优先级由硬件线路或查询程序的查询顺序决定&#xff0c;不可动态改变。处理优先级可利用中断屏蔽技术动态调整&#xff0c;以实现多重中断。下面来看他们如何运用在中断处理过程中&#xff1a; 中断控制器位于CPU和外设之…

SpringBoot开发:古典舞在线交流平台的架构与实现

第三章 系统分析 3.1 可行性分析 需要使用大部分精力开发的古典舞在线交流平台为了充分降低开发风险&#xff0c;特意在开发之前进行可行性分析这个验证系统开发是否可行的步骤。本文就会从技术角度&#xff0c;经济角度&#xff0c;还有操作角度等进行综合阐述。 3.1.1技术可行…

排序01 多目标模型

引入 使用机器学习方法对指标做预估&#xff0c;再对预估分数做融合。融合方法&#xff1a;加权和方法给不同指标赋予不同的权重&#xff0c;权重是做A/B test调试得到的。还有更好地融合方法。 多目标模型 排序模型的输入是各种各样的特征&#xff0c;用户特征主要是用户id和…

易趋(EasyTrack)资深顾问唐颖受邀为第四届中国项目经理大会演讲嘉宾

全国项目经理专业人士年度盛会 易趋&#xff08;EasyTrack&#xff09;资深顾问唐颖女士受邀为PMO评论主办的全国项目经理专业人士年度盛会——2024第四届中国项目经理大会演讲嘉宾&#xff0c;演讲议题为“隐形翅膀——数字化项目管理助力项目经理鹏程万里”。大会将于10月26-…

古典舞在线互动:SpringBoot平台设计与功能实现

第三章 系统分析 3.1 可行性分析 需要使用大部分精力开发的古典舞在线交流平台为了充分降低开发风险&#xff0c;特意在开发之前进行可行性分析这个验证系统开发是否可行的步骤。本文就会从技术角度&#xff0c;经济角度&#xff0c;还有操作角度等进行综合阐述。 3.1.1技术可行…

Prometheus之Pushgateway使用

Pushgateway属于整个架构图的这一部分 The Pushgateway is an intermediary service which allows you to push metrics from jobs which cannot be scraped. The Prometheus Pushgateway exists to allow ephemeral and batch jobs to expose their metrics to Prometheus. S…

扩散引导语言建模(DGLM):一种可控且高效的AI对齐方法

随着大型语言模型(LLMs)的迅速普及,如何有效地引导它们生成安全、适合特定应用和目标受众的内容成为一个关键挑战。例如,我们可能希望语言模型在与幼儿园孩子互动时使用不同的语言,或在撰写喜剧小品、提供法律支持或总结新闻文章时采用不同的风格。 目前,最成功的LLM范式是训练…