并发编程系列(二):线程锁使用场景和原理

C# 多线程编程:线程锁与无锁并发 多线程编程在现代软件开发中至关重要。本文将讨论 C# 中的多线程技术,重点介绍锁的概念,线程锁与无锁并发。通过学习本篇博文,我们将学会如何正确处理并发问题,提高程序的性能和稳定性。锁是一种同步机制,用于控制多个线程对共享资源的访问。当一个线程获得了锁时,其他线程将被阻塞,直到该线程释放了锁。本文深入探讨了 C# 中的多线程编程技术,重点介绍了锁的基本概念、线程锁的类型、锁的实现方式、无锁并发编程以及 C# 中的并发集合类和经典并发同步问题。理解多线程编程的基本概念。 阅读详情

问题:java中如何解决线程并行导致的数据安全问题?
java SE 1.6后对synchronized做了优化,增加了偏向锁、轻量级锁概念,以减少获得/释放锁带来的性能消耗

一、synchronized的使用
锁的粒度,从上到下变小
1.修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁

public synchronized static void demo2() {
    // 代码块
}

2.修饰实例方法,作用于当前实例对象加锁,进入同步代码前要获得当前实例对象的锁

public synchronized void demo2() {
    // 代码块
}

3.修饰代码块,指定加锁对象,进入同步代码前要获得给定对象的锁

public void demo(){
    synchronized (this){ 
        // 代码块
    }
}

二、锁是如何存储的

  • 问题:锁如何实现线程的互斥特性?
  • 思路:synchronized(lock)为切入点,锁是否与对象生命周期有关,锁在对象中存在状态是怎样的

1.对象在内存中存储

对象头(header),实例数据(instance data),对齐填充(padding)
eg.

2.jvm源码实现
new 实例对象时,(Hotspot虚拟机)JVM会创建instanceOopDesc对象。
OOP-klass模型描述对象实例,OOP(Ordinary ObjectPoint)普通对象指针,klass描述对象具体类型。Hotspot采用instanceOopDesc和arrayOopDesc类型描述对象头

instanceOopDesc,继承自OopDesc类。OopDesc,定义在oop.hpp,OopDesc对象包含两个成员_mark和metadata。
_mark,表示对象标记,属于markOop类型,也即Mark Word,它记录了对象和锁有关的信息
_metadata,表示类元信息,类元信息存储的是对象指向它的类元数据(Klass)的首地址,其中Klass表示普通指针,_compressed_klass表示压缩类指针

MarkWord
markOop,定义在markOop.hpp文件,代码如下


Mark Word记录了对象和锁有关的信息,里面存储的信息会随着锁标志位的变化而变化。
Mark Word可能存在5种变化情况,以下演示32位机的(也有64位机的,存储的大小不一样)

eg.记录5种状态图表

锁状态

25bit

4bit

1bit

2bit

23bit

2bit

是否偏向锁

锁标志位

无锁

对象的HashCode

分代年龄

0

01

偏向锁

线程ID

epoch

分代年龄

1

01

轻量级锁

指向栈中锁记录的指针

00

重量级锁

指向重量级锁的指针

10

GC标记

空

11

3.为什么任何对象都可以实现锁
任何一个对象都继承Object对象,object在jvm中都有native的C++对象 oop/oopDesc对应。
线程在获取锁时,其实是获取监视器对象monitor,多线程访问同步代码块时,相当于去争抢对象监视器,以修改对象的锁标识。

三、锁升级
无锁,偏向锁,轻量级锁,重量级锁
1.偏向锁
当只有一个线程访问时,会将MarkWorld锁标志位设为“01”,并通过CAS操作把线程ID记录到MarkWord。当下次访问时,无需再次加锁和释放锁,会判断对象头里是否有当前线程ID,若有,则无需尝试获得锁(也即是锁会偏向于某个线程)
eg.偏向锁流程

2.轻量级锁
当一个线程Thread-1已经持有偏向锁,还没走完同步代码,此时若有竞争线程Thread-2尝试进入,则会将持有偏向锁线程先暂停,然后升级为轻量级锁。
竞争线程Thread-2通过自旋方式,有限次数内获得锁,如果自旋获取锁失败,则当前线程升级为重量级锁,竞争线程挂起阻塞

轻量级锁升级细节

在线程栈帧中创建锁记录,将MarkWord中标记的对象信息复制到锁记录中,将锁记录的Owner指向锁对象,将MarkWord替换为指向锁记录的指针

轻量级锁的解锁

轻量级锁的释放,获得锁的逆向逻辑。通过CAS操作将线程栈帧中的lockRecord替换回MarkWord中,若成功表示没有竞争;若失败,则存在竞争,轻量级锁会膨胀为重量级锁

eg.轻量级锁流程

3.重量级锁
若两个线程正常交替执行,那么轻量级锁基本能够满足锁的要求,若当两个线程同时进入临界区,则轻量级锁会膨胀为重量级锁,未获得锁的线程挂起阻塞

通过命令:javap -v 类.class,查看生成的class文件信息

加了同步的代码会看到monitorenter和monitorexit.
每个java对象都会与一个监视器monitor关联,要想取得synchronized修饰的同步方法或代码块的执行权限,首先需要取得修饰对象的monitor。
monitorenter表示去获得一个对象监视器,monitorexit表示释放监视器所有权,使其他被阻塞线程可以尝试获得监视器。

eg.重量级锁加锁流程


任意对Object访问的线程,首先要获得Object的监视器

四、wait,notify,notifyAll

  • 问题:通过synchronized,阻塞线程只有等到获得锁线程执行完毕才唤醒,如何显示控制线程唤醒?
  • 通过信号机制:Object提供的wait,notify,notifyAll控制线程状态。

基本概念
wait,持有对象锁线程A,准备释放对象锁权限,释放CPU资源进入等待状态。
notify,当持有对象锁线程A准备释放锁权限时,通知jvm唤醒某个竞争该锁的线程,当A执行完毕并释放锁,线程X直接获得对象锁权限,其他线程继续等待。
notifyAll,唤醒所有竞争统一对象锁的线程。

五、练习

1.请分析以下程序的执行结果,并详细说明原因

public class SynchronizedDemo implements Runnable{
    int x = 100;

    public synchronized void m1() {
        x = 1000;
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("x=" + x);
    }

    public synchronized void m2() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        x = 2000;
    }
    public static void main(String[] args) throws InterruptedException {
        SynchronizedDemo sd = new SynchronizedDemo();
        new Thread(()->sd.m1()).start();
        new Thread(()->sd.m2()).start();
        sd.m2();
        System.out.println("Main x=" + sd.x);
    }
    @Override
    public void run() {
        m1();
    }
}

分析:

SynchronizedDemo sd = new SynchronizedDemo();
        new Thread(()->sd.m1()).start(); // x=1000,休眠1秒
        new Thread(()->sd.m2()).start(); // 休眠0.2秒,x=2000
        sd.m2(); // 休眠0.2秒,x=2000
        System.out.println("Main x=" + sd.x);

Main,t1(执行m1方法),t2(执行m2方法)

Main线程获得锁,x=2000
    打印时,若t1获得锁,并先于打印给x=1000,则打印Main x=1000,休眠1秒打印x=1000
    打印时,若t1获得锁,晚于打印或者t2获得锁,则输出Main x=2000,再输出x=1000

t1获得锁,打印x=1000,再打印Main x=2000(不管main还是t2获得锁,结果一样)
t2获得锁,x赋值2000
    Main获得锁后,在打印时,若t1先于打印把x赋值1000,则输出Main=1000,x=1000
    t1获得锁,x=1000,再打印Main x=2000

2.下面这个程序的最终结果是多少?为什么?

public class SynchronizedDemo  {
   static Integer count=0;
   public static void incr(){
       synchronized (count) {
           try {
               Thread.sleep(1);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           count++;
       }
   }
    public static void main(String[] args) throws IOException, InterruptedException {
        for(int i=0;i<1000;i++){
            new Thread(()->SynchronizedDemo.incr()).start();
        }
        Thread.sleep(5000);
        System.out.println("result:"+count);
    }
}

分析:Integer可变,所以基本可以视为没有加锁,也就是存在并发问题,值会小于1000

4种常用Java线程锁的特点,性能比较、使用场景.pdf 4种常用Java线程锁的特点,性能比较、使用场景 线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。 立即下载

相关推荐

基于MySQL的京东用户行为分析

对京东用户行为的分析,利用AARRR模型进行分析。

SelenaSelena的博客 1911

【Code】4种常用Java线程锁的特点,性能比较、使用场景

本文是对相关主题文章的代码功能展示,主要通过代码形式来验证和演示功能,以加深对知识点的理解。如有遗漏或理解不正确的地方,欢迎大家拍砖!

爱码少年 00fly.online 的博客 2万+

Lane、PHY和Preset Value Bit

每个Lane支持不同的传输速度和带宽,例如PCIe 3.0中,一个Lane的传输速度可以达到8 GT/s(Giga Transfers per second),也就是1 GB/s(GigaBytes per second)。Preset Value Bit(预置值位)是在PCIe 3.0的Lane上新引入的一种机制,用于优化信号传输质量。PCIe设备可以包含多个Lane,例如x1、x4、x8、x16等,表示该设备同时使用的Lane数目,比如x16表示该设备采用了16个Lane,具有更高的传输速度和带宽。

weixin_58083606的博客 1296

多线程线程锁的概念和介绍

在多线程环境下,为了让多线程安全地访问和使用共享变量,必须引入锁机制。锁机制即当一个线程持有锁后,其他线程只能进行等待,直到持有锁的线程释放锁,再次重新竞争锁。

Jay的小提琴的博客 2110

基于Pixhawk与OpenMV的无人机视觉引导精准降落系统设计与实现

本文详细介绍了基于Pixhawk飞控与OpenMV视觉模块的无人机精准降落系统设计与实现。系统通过OpenMV识别Apriltag标签,结合MAVLink通信将视觉数据发送给Pixhawk,实现厘米级精准降落。文章涵盖硬件连接、软件配置、核心代码逻辑解析及实战调试技巧,为无人机自动充电、物流配送等场景提供可靠解决方案。

weixin_29189377的博客 320

鸿蒙子系统解读-分布式软总线子系统初步研究(下)

鸿蒙子系统解读-分布式软总线子系统初步研究(下) 本文作者:江苏润和软件股份有限公司 郎建中 3.2. 数据传输 上一节我们看了PublishService()函数的代码实现过程。被发现端发布服务后,软总线回调发布成功的函数中,被发现端需要使用CreateSessionServer()来创建会话的服务器等待发现端的连接创建会话。下面我们就来分析CreateSessionServer()函数的实现过程。 数据传输部分的代码位于foundation/communication/services/softbus

weixin_47070198的博客 3089

线程中的锁

是指分布在不同任务之间的若干程序片断,它们的运行必须严格按照规定的某种先后次序来运行,这种先后次序依赖于要完成的特定的任务。最基本的场景就是:两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序运行。是指分步在不同任务之间的若干程序片断,当某个任务运行其中一个程序片段时,其它任务就不能运行它们之中的任一程序片段,只能等到该任务运行完这个程序片段后才可以运行。最基本的场景就是:一个公共资源同一时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共资源。信号量(同步与互斥)

hello_liuniuniu的博客 821

2026年数学建模国赛论文排版规范:Word与LaTeX终极对比及模板

提前演练:不要等到比赛开始才第一次使用排版工具。至少在赛前一个月完成1-2篇模拟论文的全流程排版练习。建立检查清单:基于国赛规范制定排版检查清单,在提交前逐项核对——公式编号连续?图表引用准确?参考文献完整?页数不超限?善用模板但不止于模板:模板提供的是起点而非终点。根据自身需求调整宏包配置和样式参数,理解每一行配置的含义。关注评审体验:从评委的视角审视论文——重点内容是否突出?逻辑结构是否清晰?视觉层次是否合理?备份与容错:无论使用何种工具,建立多级备份机制。本地、云端、U盘各存一份,且定期保存历史版本。

YOLO 210

线程锁的使用场景

什么是线程锁 两个或以上线程在访问共享资源时,为了确保这个共享资源被每个线程访问结果正确,这时就需要用到线程锁。 2 线程锁使用场景 a)多个窗口同时售票问题 b)公交车前后门上车抢坐问题 3 使用线程锁,解决多个窗口同时售票问题 public class TicketTask implements Runnable{ private int ticket ...

xiaoyaGrace的博客 1002

设计原则:面向对象设计原则详解

我们在应用程序开发中,一般要求尽量两做到可维护性和可复用性。 应用程序的复用可以提高应用程序的开发效率和质量,节约开发成本,恰当的复用还可以改善系统的可维护性。而在面向对象的设计里面,可维护性复用都是以面向对象设计原则为基础的,这些设计原则首先都是复用的原则,遵循这些设计原则可以有效地提高系统的复用性,同时提高系统的可维护性。面向对象设计原则和设计模式也是对系统.........

黄规速博客:学如逆水行舟,不进则退 4万+

《QGIS快速入门与应用基础》108:CGCS2000对应的投影坐标系

本文详细解析了CGCS2000地理坐标系对应的各类投影坐标系,包括高斯-克吕格3°带/6°带、UTM、地方独立投影和Web墨卡托投影。文章系统阐述了各投影类型的分带规则、参数设置、精度特性及适用场景,并提供了EPSG编码对照表和QGIS操作指南。通过"基准+投影"的绑定机制,这些投影坐标系实现了平面坐标与经纬度的精准转换,满足从大比例尺工程测量到Web地图服务等不同应用需求。内容涵盖投影原理、坐标格式、变形控制等关键技术要点,为GIS开发者和测绘人员提供了实用的参考信息。

LuckyHanMo的博客 542

别再用错锁!Java 锁机制的场景化应用全解析

Java 中的锁机制丰富多样,每种锁都有其独特的特性和适用场景。在实际开发中,需要根据具体的业务需求、并发量以及性能要求,合理选择合适的锁。对于简单的同步需求,Synchronized 是便捷的选择;对于复杂逻辑和特定功能需求,ReentrantLock提供了更强大的功能;在读写操作比例差异较大的场景下,读写锁能显著提升性能;而自旋锁则适用于锁占用时间短的高并发场景。通过正确选择和使用锁机制,能够构建出高效、安全的多线程 Java 应用程序。

z1ztai的博客 970

顶级AI大模型白嫖ChatGPT Plus1个月,0成本薅顶配功能

Chat*** Plus限时免费1个月!原价20美元/月的顶配功能

BUG猿的博客 7278

java的线程锁

竞态条件是指多个线程在并发执行时,由于执行顺序的不确定性,导致程序行为不可预测。线程锁可以通过确保同一时刻只有一个线程访问共享资源,来避免这种情况。通过合理设计锁的获取和释放顺序,可以避免死锁的发生。当一个线程获取锁时,其他线程必须等待,直到该线程释放锁。若两个线程同时执行,它们可能会发生死锁,因为每个线程都在等待对方释放自己所需要的锁。线程锁是多线程编程中的一个关键概念,通过合理使用锁机制,可以提高程序的稳定性和可靠性。:最基础的锁机制,用于方法或代码块,保证同一时刻只有一个线程访问共享资源。

2303_80059852的博客 2462

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

高并发:线程、线程锁与线程池(精华)

前文: 单线程——多线程的开启——线程锁——线程同步工具——手写连接池——连接池工具类。 一、线程 1.线程的概念 2.线程与进程的关系 3.定义: 区别:如上!!! 4.wait()和sleep() 5.线程的状态及其他API 二、线程锁 线程并发同步时,引入了锁机制。 1. 普通锁机制:synchronized修饰代码块与...

weixin_42687074的博客 3896

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

线程与线程锁

线程和线程锁是并发编程中的核心概念。理解线程的工作原理以及如何使用线程锁来管理并发访问共享资源,对于编写高效、安全的并发程序至关重要。以下是线程与线程锁的机制原理与使用的详细说明。掌握线程与线程锁的机制原理与使用,可以帮助你编写高效、安全的并发程序。

blue_msboy的博客 1000

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

python 线程锁

线程锁是一种同步原语,用于协调多个线程对共享资源的访问。它的主要目的是防止多个线程同时修改同一资源,从而避免数据竞争和不一致性问题。当一个线程需要访问共享资源时,它首先尝试获取锁。如果锁是可用的,该线程获得锁,并可以安全地访问共享资源。如果锁已被其他线程持有,当前线程将被阻塞,直到锁被释放。线程完成对共享资源的访问后,必须释放锁,以允许其他线程访问该资源。通过这种机制,线程锁确保在任何给定时刻,只有一个线程可以访问受保护的共享资源,从而维护了数据的一致性和完整性。

cliffordl的专栏 1435

高并发编程系列:4种常用Java线程锁的特点,性能比较、使用场景

转载自:高并发编程系列:4种常用Java线程锁的特点,性能比较、使用场景 多线程的缘由 在出现了进程之后,操作系统的性能得到了大大的提升。虽然进程的出现解决了操作系统的并发问题,但是人们仍然不满足,人们逐渐对实时性有了要求。 使用多线程的理由之一是和进程相比,它是一种非常花销小,切换...

chengyu5033的博客 115

unity C#什么是线程锁,以及使用案例

线程锁在硬件层面通常配合内存屏障(Memory Barrier)使用,确保缓存中的数据在锁的获取和释放时与主内存保持一致,即修改后的数据会被及时刷新到主内存,而其他线程在获取锁后能从主内存重新加载最新的数据。当一个线程进入临界区时,它会先获取锁,此时其他试图获取该锁的线程将被阻塞或挂起,直到持有锁的线程执行完临界区内的代码,并释放锁。为了避免死锁的发生,线程锁的使用需要遵循一定的规则,如避免循环等待锁的情况,以及在持有锁的情况下尽量不要尝试获取其他锁(除非满足特定的锁顺序)。这样的互斥量来实现线程锁。

qqrrjj2011的博客 1597

快速理解线程锁

线程锁 线程锁真的好麻烦啊!!! 找了几篇博客发现写的都不一样 相关联内容太多不容易理解 所以现在需要理清 什么是线程锁 应用场景 怎么用 优缺点 1. 什么是线程锁机制 多线程可以同时运行多个任务 但是当多个线程同时访问共享数据时,可能导致数据不同步,甚至错误! so,不使用线程锁, 可能导致错误 啰嗦两句: 比如你在银行取钱的同时你女朋友用支付宝取钱 ...

宅神的博客 2万+

线程锁及线程锁的作用

什么是线程锁机制   多线程可以同时运行多个任务但是当多个线程同时访问共享数据时,可能导致数据不同步,甚至错误!   so,不使用线程锁, 可能导致错误 分布式锁,进程锁,线程锁到底是什么 在分布式集群系统的开发中,线程锁往往并不能支持全部场景的使用,必须引入新的技术方案分布式锁。   线程锁:大家都不陌生,主要用来给方法、代码块加锁。当某个方法或者代码块使用锁时,那么在同一时刻至...

weixin_30421809的博客 2100

深入解析Java线程锁机制:从原理到最佳实践

在多线程编程中,线程安全是开发者面临的核心挑战之一。当多个线程并发访问共享资源时,如果没有正确的同步机制,就可能出现数据不一致、竞态条件等问题。Java提供了多种线程锁机制来保障线程安全,本文将深入探讨Java中的各种锁实现、使用场景和优化策略。

keshi12354的博客 1009

线程锁的原理是什么

线程锁的使用 本文内容何时该使用线程锁. 线程锁的写法. 以线程锁的例子来理解线程的调度。使用线程锁的场合程序中经常采用多线程处理,这可以充分利用系统资源,缩短程序响应时间,改善用户体验;如果程序中只使用单线程,那么程序的速度和响应无疑会大打折扣。 但是,程序采用了多线程后,你就必须认真考虑线程调度的问题,如果调度不当,要么造成程序出错,要么造成荒谬的结果。一个讽刺僵化体制的笑话前苏联某官员

qew110123的专栏 2478

java 并发_最全Java并发编程技能:多线程+线程池+线程锁+并发工具+并发容器

Java并发编程的技能基本涵括以下5方面:多线程线程池线程锁并发工具类并发容器多线程的4种创建方式继承Thread实现Runnable接口实现Callable接口以及线程池来创建线程Java线程的生命周期线程的创建线程的就绪线程的运行线程的阻塞线程的死亡详细请查看:Java并发编程系列:线程的五大状态,以及线程之间的通信与协作Java线程的通信与协作、1.线程就绪:调用start(),以及slee...

weixin_39854730的博客 112

Python线程锁:确保多线程程序的数据安全

Python的线程锁是一个强大的工具,用于在多线程环境中管理共享资源和。

IT策士的博客 1265
上一篇: 并发编程系列(一):线程基础
下一篇: 并发编程系列(三):重排序和可见性问题
OpenWorld1
博客等级 码龄12年 1粉丝 42原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值