多线程锁

news2025/1/17 0:28:16

在并发编程中,锁(Lock)是一种用于控制多个线程对共享资源访问的机制。正确使用锁可以确保数据的一致性和完整性,避免出现竞态条件(Race Condition)、死锁(Deadlock)等问题。Java 提供了多种类型的锁来满足不同场景下的需求,从内置的对象锁到更高级别的 java.util.concurrent.locks 包中的显式锁。

为什么需要锁?

当多个线程同时尝试修改同一份数据时,如果没有适当的同步措施,可能会导致以下问题:

  • 数据不一致:不同的线程读取到了部分更新的数据。
  • 丢失更新:一个线程的更改被另一个线程覆盖。
  • 脏读/写:线程读取或写入了尚未完成的操作。

为了解决这些问题,我们需要一种机制来协调线程之间的访问顺序,保证每次只有一个线程能够操作共享资源。这就是锁的作用所在。

Java 中的锁类型

内置锁(Intrinsic Lock)

也称为监视器锁(Monitor Lock),是通过关键字 synchronized 实现的。每个对象都有一个与之关联的内置锁,当一个线程进入由 synchronized 修饰的方法或代码块时,它会自动获取该对象的锁;而当方法执行完毕或遇到 wait() 调用时,则会释放锁。

public synchronized void method() {
    // 只有一个线程可以进入这里
}
public void method() {
    synchronized (this) {
        // 同样只有一个线程可以进入这里
    }
}

显式锁(Explicit Lock)

从 Java 5 开始,java.util.concurrent.locks 包引入了更为灵活和强大的锁接口——Lock。相比于内置锁,显式锁提供了更多的功能,如可中断的锁等待、超时获取锁以及公平锁等特性。

ReentrantLock

ReentrantLock 是最常用的显式锁实现之一,它允许同一个线程多次获取同一把锁,并且必须按照获取的次数逐一释放。这使得递归调用成为可能。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Counter {
    private int count = 0;
    private final Lock lock = new ReentrantLock();

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock(); // 确保无论如何都会解锁
        }
    }

    public int getCount() {
        return count;
    }
}
ReadWriteLock

ReadWriteLock 接口定义了一种读写分离的锁机制,其中读锁允许多个线程同时持有,但不允许写锁存在;反之,一旦有线程持有了写锁,其他所有试图获取读锁或写锁的线程都将被阻塞。这种设计非常适合读多写少的情况。

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Cache {
    private final Map<String, Object> map = new HashMap<>();
    private final ReadWriteLock rwl = new ReentrantReadWriteLock();

    public Object get(String key) {
        rwl.readLock().lock();
        try {
            return map.get(key);
        } finally {
            rwl.readLock().unlock();
        }
    }

    public void put(String key, Object value) {
        rwl.writeLock().lock();
        try {
            map.put(key, value);
        } finally {
            rwl.writeLock().unlock();
        }
    }
}
StampedLock

StampedLock 是 Java 8 新增的一种乐观锁,它结合了读写锁的功能并提供了更好的性能优化。它支持三种模式:读、写和乐观读。乐观读模式假设不会有冲突发生,因此不会阻塞其他线程;但如果检测到冲突,则需要回滚操作并重新尝试。

import java.util.concurrent.locks.StampedLock;

public class Point {
    private double x, y;
    private final StampedLock sl = new StampedLock();

    void move(double deltaX, double deltaY) { 
        long stamp = sl.writeLock();
        try {
            x += deltaX;
            y += deltaY;
        } finally {
            sl.unlockWrite(stamp);
        }
    }

    double distanceFromOrigin() { 
        long stamp = sl.tryOptimisticRead();
        double currentX = x, currentY = y;
        if (!sl.validate(stamp)) {
            stamp = sl.readLock();
            try {
                currentX = x;
                currentY = y;
            } finally {
                sl.unlockRead(stamp);
            }
        }
        return Math.sqrt(currentX * currentX + currentY * currentY);
    }
}

锁的使用原则

  • 最小化锁定范围:尽量减少加锁的时间和区域,只保护真正需要保护的资源。
  • 避免死锁:设计时要特别小心,防止形成循环等待链,即多个线程互相持有对方所需的锁。
  • 使用超时机制:对于可能长时间阻塞的操作,考虑设置合理的超时时间以提高系统的健壮性。
  • 优先选择高级并发工具:相比于原始的内置锁,应该更多地利用 java.util.concurrent 包中提供的高级工具,因为它们通常更加安全可靠且易于使用。
  • 文档化锁协议:清晰地记录各个锁之间的关系和使用规则,有助于后续维护人员理解代码逻辑。

最佳实践案例

使用 ReentrantLock 替代内置锁

在某些情况下,ReentrantLock 可以为我们提供比内置锁更多的灵活性。例如,它可以让我们实现非阻塞的锁尝试 (tryLock) 或者带超时的锁等待 (tryLock(long timeout, TimeUnit unit)).

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;

public class BankAccount {
    private double balance;
    private final Lock lock = new ReentrantLock();

    public boolean withdraw(double amount) throws InterruptedException {
        if (lock.tryLock(1, TimeUnit.SECONDS)) {
            try {
                if (balance >= amount) {
                    balance -= amount;
                    return true;
                }
            } finally {
                lock.unlock();
            }
        }
        return false; // 超时未获得锁或余额不足
    }
}

使用 ReadWriteLock 优化读多写少场景

当应用程序中有大量读操作而写操作相对较少时,ReadWriteLock 可以显著提升性能,因为它允许多个线程同时进行读取,而不必像内置锁那样每次都排他地占用资源。

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ConcurrentCache<K, V> {
    private final Map<K, V> cache = new ConcurrentHashMap<>();
    private final ReadWriteLock rwl = new ReentrantReadWriteLock();

    public V get(K key) {
        rwl.readLock().lock();
        try {
            return cache.get(key);
        } finally {
            rwl.readLock().unlock();
        }
    }

    public void put(K key, V value) {
        rwl.writeLock().lock();
        try {
            cache.put(key, value);
        } finally {
            rwl.writeLock().unlock();
        }
    }
}

使用 StampedLock 进行乐观读

对于那些读操作远多于写操作,并且读操作之间几乎没有竞争的应用程序来说,StampedLock 的乐观读模式可以提供更高的吞吐量。它首先尝试无锁读取,只有在检测到潜在冲突时才会退回到传统的读锁方式。

import java.util.concurrent.locks.StampedLock;

public class OptimisticCounter {
    private long count = 0;
    private final StampedLock sl = new StampedLock();

    public long readCount() {
        long stamp = sl.tryOptimisticRead();
        long localCount = count;
        if (!sl.validate(stamp)) {
            stamp = sl.readLock();
            try {
                localCount = count;
            } finally {
                sl.unlockRead(stamp);
            }
        }
        return localCount;
    }

    public void increment() {
        long stamp = sl.writeLock();
        try {
            count++;
        } finally {
            sl.unlockWrite(stamp);
        }
    }
}

结语

感谢您的阅读!如果您对多线程锁或其他并发编程话题有任何疑问或见解,欢迎继续探讨。

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

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

相关文章

28.找出字符串中第一个匹配项的下标【力扣】KMP前缀表 ≈ find() 函数、暴力解法

class Solution { public: //得到前缀表void getNext(int *next,string needle){int j0;for(int i1;i<needle.size();i){while(j>0 && needle[j]!needle[i]) jnext[j-1];//**j>0**>j0是出口if(needle[i]needle[j]) j;next[i]j;//若写入if中&#xff0c;则该…

当自动包布机遇上Profinet转ModbusTCP网关,“妙啊”,工业智能“前景无限

在自动化控制技术日新月异的当下&#xff0c;Profinet与ModbusTCP这两种协议在工业通信领域占据着举足轻重的地位。ModbusTCP是基于以太网的串行通信协议&#xff0c;而Profinet则是依托工业以太网的现场总线协议。它们在数据传输速度、实时性表现以及兼容性等方面各具特色。不…

ADC(Analog-to-digital converter)模拟-数字转换器

ADC简介 ADC&#xff08;Analog-to-Digital Converter&#xff09;&#xff0c;即模拟-数字转换器&#xff0c;是一种将模拟信号转换成数字信号的电子设备。它在现代电子系统中扮演着至关重要的角色&#xff0c;广泛应用于传感器信号处理、通信系统、医疗设备、工业自动化等多…

Uniapp判断设备是安卓还是 iOS,并调用不同的方法

在 UniApp 中&#xff0c;可以通过 uni.getSystemInfoSync() 方法来获取设备信息&#xff0c;然后根据系统类型判断当前设备是安卓还是 iOS&#xff0c;并调用不同的方法。 示例代码 export default {onLoad() {this.checkPlatform();},methods: {checkPlatform() {// 获取系…

TP4056锂电池充放电芯片教程文章详解·内置驱动电路资源!!!

目录 TP4056工作原理 TP4056引脚详解 TP4056驱动电路图 锂电池充放电板子绘制 编写不易&#xff0c;仅供学习&#xff0c;感谢理解。 TP4056工作原理 TP4056是专门为单节锂电池或锂聚合物电池设计的线性充电器&#xff0c;充电电流可以用外部电阻设定&#xff0c;最大充电…

平滑算法 效果比较

目录 高斯平滑 效果对比 移动平均效果比较: 高斯平滑 效果对比 右边两个参数是1.5 2 代码: smooth_demo.py import numpy as np import cv2 from scipy.ndimage import gaussian_filter1ddef gaussian_smooth_array(arr, sigma):smoothed_arr = gaussian_filter1d(arr, s…

Jenkins-简介/安装!

一. 关于持续集成&#xff1a; 持续集成(CI ) [ Continuous Integration ]&#xff0c;通俗来讲&#xff0c;就是一个能监控版本控制系统变化的工具&#xff0c;可以自动编译和测试集成的应用程序。出现问题&#xff0c;能够及时的通知相应人员。持续集成是一种思维工具集&…

Flutter中Get.snackbar避免重复显示的实现

在pubspec.yaml中引入依赖框架。 #GetX依赖注解get: ^4.6.5创建一个SnackBarManager管理类去管理每个提示框。 import package:get/get.dart; import package:flutter/material.dart;class SnackBarManager {factory SnackBarManager() > instance;static final SnackBarMa…

c#删除文件和目录到回收站

之前在c上遇到过这个问题&#xff0c;折腾许久才解决了&#xff0c;这次在c#上再次遇到这个问题&#xff0c;不过似乎容易了一些&#xff0c;亲测代码如下&#xff0c;两种删除方式都写在代码中了。 直接上完整代码&#xff1a; using Microsoft.VisualBasic.FileIO; using Sy…

微信小程序集成Vant Weapp移动端开发的框架

什么是Vant Weapp Vant 是一个轻量、可靠的移动端组件库&#xff0c;于 2017 年开源。 目前 Vant 官方提供了 Vue 2 版本、Vue 3 版本和微信小程序版本&#xff0c;并由社区团队维护 React 版本和支付宝小程序版本。 官网地睛&#xff1a;介绍 - Vant Weapp (vant-ui.gith…

kafka原理和实践

Kafka是当前分布式系统中最流行的消息中间件之一&#xff0c;凭借着其高吞吐量的设计&#xff0c;在日志收集系统和消息系统的应用场景中深得开发者喜爱。本篇就聊聊Kafka相关的一些知识点。主要包括以下内容&#xff1a; Kafka简介 Kafka特点Kafka基本概念Kafka架构Kafka的几…

【机器学习】数据拟合-最小二乘法(Least Squares Method)

最小二乘法&#xff08;Least Squares Method&#xff09; 最小二乘法是一种广泛使用的数据拟合方法&#xff0c;用于在统计学和数学中找到最佳拟合曲线或模型&#xff0c;使得观测数据点与模型预测值之间的误差平方和最小化。以下是详细介绍&#xff1a; 基本概念 假设有一组…

我的年度总结

这一年的人生起伏&#xff1a;从曙光到低谷再到新的曙光 其实本来没打算做年度总结的&#xff0c;无聊打开了帅帅的视频&#xff0c;结合自己最近经历的&#xff0c;打算简单聊下。因为原本打算做的内容会是一篇比较丧、低能量者的呻吟。 实习生与创业公司的零到一 第一段工…

30分钟内搭建一个全能轻量级springboot 3.4 + 脚手架 <5> 5分钟集成好caffeine并使用注解操作缓存

快速导航 <1> 5分钟快速创建一个springboot web项目 <2> 5分钟集成好最新版本的开源swagger ui&#xff0c;并使用ui操作调用接口 <3> 5分钟集成好druid并使用druid自带监控工具监控sql请求 <4> 5分钟集成好mybatisplus并使用mybatisplus generator自…

Mysql--运维篇--主从复制和集群(主从复制I/O线程,SQL线程,二进制日志,中继日志,集群NDB)

一、主从复制 MySQL的主从复制&#xff08;Master-Slave Replication&#xff09;是一种数据冗余和高可用性的解决方案&#xff0c;它通过将一个或多个从服务器&#xff08;Slave&#xff09;与主服务器&#xff08;Master&#xff09;同步来实现。主从复制的基本原理是&#…

实战threeJS数字孪生开源 数字工厂

threeJS数字孪生 数字工厂 设备定位 基于three.js的数字工厂开源项目介绍 一、项目概述 本项目是一款基于three.js的数字工厂项目&#xff0c;旨在通过3D可视化技术&#xff0c;为工业制造领域提供一个直观、高效、智能的生产监控与管理平台。该项目结合了现代前端技术栈&…

回归预测 | MATLAB实RVM相关向量机多输入单输出回归预测

回归预测 | MATLAB实RVM相关向量机多输入单输出回归预测 目录 回归预测 | MATLAB实RVM相关向量机多输入单输出回归预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 RVM-Adaboost相关向量机集成学习多输入单输出回归预测是一种先进的机器学习方法&#xff0c;用于处理…

计算机网络 (44)电子邮件

一、概述 电子邮件&#xff08;Electronic Mail&#xff0c;简称E-mail&#xff09;是因特网上最早流行的应用之一&#xff0c;并且至今仍然是因特网上最重要、最实用的应用之一。它利用计算机技术和互联网&#xff0c;实现了信息的快速、便捷传递。与传统的邮政系统相比&#…

向量数据库Milvus详解

向量数据库Milvus详解 0. 什么是向量数据库? 在现实世界中,并非所有数据都可以整齐地放到行和列中。在处理图像、视频和自然语言等复杂的非结构化数据时尤其如此。这就是向量数据库的用武之地。 向量数据库是一种以高维向量的形式来存储数据的数据库,这些向量本质上是表示…

通信与网络安全管理之ISO七层模型与TCP/IP模型

一.ISO参考模型 OSI七层模型一般指开放系统互连参考模型 (Open System Interconnect 简称OSI&#xff09;是国际标准化组织(ISO)和国际电报电话咨询委员会(CCITT)联合制定的开放系统互连参考模型&#xff0c;为开放式互连信息系统提供了一种功能结构的框架。 它从低到高分别是…