并发编程 synchronized (一) 初识

并发编程之synchronized干货详解 synchronized在偏向锁升级到轻量锁时,会涉及到偏向锁撤销,需要等到一个安全点,stw,才可以撤销,并发偏向锁撤销比较消耗资源 在程序启动时,偏向锁有一个延迟开启的操作,因为项目启动时,ClassLoader会加载.class文件,这里会涉及到synchronized操作, 为了避免启动时,涉及到偏向锁撤销,导致启动效率变慢,所以程序启动时,默认不是开启偏向锁的。线程基于CAS修改数据的方式:先获取主内存数据,在修改之前,先比较数据是否一致,如果一致修改主内存数据,如果不一致,放弃这次修改。 阅读详情

目录

上下文切换对非原子操作的影响

示例

原因

 详细分析

临界区 Critical Section

竞态条件 Race Condition

通过 synchronized 解决

思考

面向对象改进


上下文切换对非原子操作的影响

示例

我们都知道,两个线程 Thread0 与 Thread1,对同一个量 count 进行操作;一个让其自增,一个让其自减,次数相等;结果可能为 负数、正数、0(极少)

示例代码:

package com.juc.ppy.synchronize;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.UnsafeCount")
public class UnsafeCount {
    static int count = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for(int i = 0; i < 50000; i++){
                count++;
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            for(int i = 0; i < 50000; i++){
                count--;
            }
        }, "t2");

        // 启动线程 t1 t2
        t1.start();
        t2.start();
        // 主线程等待 t1 t2 执行完毕
        t1.join();
        t2.join();
        log.debug("最终结果{}", count);
    }
}

原因

上下文:存储器与程序计数器中线程执行的进度信息

上下文切换:  ①Thread0 时间片用完,操作系统保存 Thread0 上下文到内存,Thread0 暂停运行,让出处理器使用权(切出)

②加载 Thread1 的上下文,Thread1 获得处理器使用权,开始或继续运行(切入)......

③Thread0 再次获取处理器使用权,继续执行

原子性:一个或一系列操作不会被线程调度(上下文切换)所打断

综上:因为静态变量 count 自增自减操作是非原子性的 + 并发情况下,线程上下文切换;导致结果出现负数与正数

 详细分析

count++ 看似只有一步操作,其实从字节码看,有四步操作

① getstatic        i // 取出静态变量 i 的值

② iconst_1 // 准备常量 1

③ iadd // 自增

④ putstatic        i // 将修改后的值存入静态变量 i

可能在 Thread0 中 count 自增之后的结果还没有重新存入 count 时,时间片用完了,轮到Thread1 使用处理器;Thread1 获取到的值还是旧值

临界区 Critical Section

一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区

count++ 与 count-- 既有读又有写 

  1. 线程运行多个线程是没有问题的
  2. 多个线程读共享资源也没有问题
  3. 当多个线程对共享资源进行读写操作时,线程切换(指令交错),就会出问题

竞态条件 Race Condition

多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件 

通过 synchronized 解决

package com.juc.ppy.synchronize;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.UnsafeCount")
public class UnsafeCount {
    static int count = 0;
    static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for(int i = 0; i < 50000; i++){
                synchronized(lock){
                    count++;
                }
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            for(int i = 0; i < 50000; i++){
                synchronized(lock){
                    count--;
                }
            }
        }, "t2");

        // 启动线程 t1 t2
        t1.start();
        t2.start();
        // 主线程等待 t1 t2 执行完毕
        t1.join();
        t2.join();
        log.debug("最终结果{}", count);
    }
}

为了避免临界区的竞态条件发生(避免在线程对共享资源进行读写时,发生线程切换对结果造成影响,即其他线程也可以读写);此处使用 synchronized 保证了临界区代码的原子性

synchronized:俗称对象锁,采用互斥方式;同一时刻,只能有一个线程拥有锁(Thread0);其他线程想获取该锁,就会被阻塞(Thread1);保证 Thread0 能正确执行完临界区代码(不代表不会发生上下文切换)

即使时间片用完,切换成 Thread1;由于 ①Thread0 还没有执行完临界区代码,②Thread1 无法获取锁,就无法执行临界区代码;③所以影响不到Thread0

 

 

思考

1. 如果把 synchronized(lock) 放在 for 循环的外面,如何理解?-- 原子性

        结果还是0,但是当 t1 获得锁之后,只有当循环结束了才会释放锁   

Thread t1 = new Thread(() -> {
            synchronized(lock){ // 锁放在循环外面
                for(int i = 0; i < 100; i++){
                    count++;
                    log.debug("最终结果{}", count);
                }
            }
        }, "t1");

Thread t2 = new Thread(() -> {
            synchronized(lock){ // 锁放在循环外面
                for(int i = 0; i < 100; i++){
                    count--;
                    log.debug("最终结果{}", count);
                }
            }
        }, "t2");

2. 如果 t1 synchronized(lock1) 而 t2 synchronized(lock2) 会怎样运作?-- 锁对象

        对象不同,相当于不同的临界区,它们互不影响;最终结果与不加锁一样

Thread t1 = new Thread(() -> {
            for(int i = 0; i < 50000; i++){
                synchronized(lock1){ // 不同锁,相当于 t1 进入的是临界区 1
                    count++;
                }
            }
        }, "t1");

Thread t2 = new Thread(() -> {
            for(int i = 0; i < 50000; i++){
                synchronized(lock2){ // 不同锁,相当于 t2 进入的是临界区 2
                    count--;
                }
            }
        }, "t2");

3. 如果 t1 synchronized(lock) 而 t2 没有加会怎么样?如何理解?-- 锁对象

        相当于 t2 不受临界区限制,t2 可以随意进入相对于 t1 的临界区;结果与不加锁一样

Thread t1 = new Thread(() -> { // 这个临界区只是针对于 t1
            for(int i = 0; i < 50000; i++){
                synchronized(lock){
                    count++;
                }
            }
        }, "t1");

Thread t2 = new Thread(() -> { // 这个所谓的"临界区"对 t2 来说相当于没有
            for(int i = 0; i < 50000; i++){
                count--;
            }
        }, "t2");

线程共用哪个资源,就在哪个位置加锁;细粒度的锁,对性能影响更小(大部分情况)。

面向对象改进

package com.juc.ppy.synchronize;

import lombok.extern.slf4j.Slf4j;

public class CriticalSection {
    private int count = 0;

    public void increase(){
        synchronized(this){
            count++;
        }
    }

    public void decrease(){
        synchronized(this){
            count--;
        }
    }

    public int get(){
        synchronized(this){
            return count;
        }
    }
}

@Slf4j(topic = "c.Test")
class Test{
    public static void main(String[] args) throws InterruptedException {
        CriticalSection cs = new CriticalSection();

        Thread t1 = new Thread(() -> {
            for(int i = 0; i < 50000; i++){
                cs.increase();
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            for(int i = 0; i < 50000; i++){
                cs.decrease();
            }
        }, "t2");

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.debug("最终结果为 {}", cs.get());
    }
}

并发编程-synchronized解决原子性问题 当多个线程并发访问某个Java对象(Object)时,无论系统如何调度这些线程,也不论这些线程如何交替操作,这个对象都能表现出一致的、正确的行为,那么对这个对象的操作是线程安全的。如果这个对象表现出不一致的、错误的行为,那么对这个对象的操作不是线程安全的,发生了线程的安全问题。//示例1@Override// 同步代码块形式——锁为this,两个线程使用的锁是一样的,线程1必须要等到线程0释放了该锁后,才能执行。 阅读详情

相关推荐

深入学习并发编程中的synchronized

可重入性是锁的一个基本要求,是为了解决自己锁死自己的情况,一个类中的同步方法调用另一个同步方法,假如Synchronized 不支持重入,进入 method2 方法时当前线程获得锁method2 方法里面执行 method1 时当前线程又要去尝试获取锁,这时如果不支持重入,它就要等释放,把自己阻塞,导致自己锁死自己。

TA016422的博客 944

临界区CriticalSection 的安全使用

临界区 是应用程序互斥多线程的常用锁。 通常分4个步骤: 初始化( InitializeCriticalSection),进入临界区(EnterCriticalSection),离开(LeaveCritclSection).,释放(DeleteCriticalSection).     隐患:          1.  InitializeCriticalSection 可能

cenchure的专栏 2913

Scala并发编程实战:Monitor与synchronized

Java并发编程最常用和易用的技术莫过于synchronized关键字,而Scala的并发编程之旅也可以从synchronized开始。而synchronized的背后其实是monitor技术。 什么是Monitor Monitor是解决并发编程问题的一种常用技术,可以有效解决互斥和同步两大常见问题,通常翻译为‘监视器’或‘管程’。个人认为‘管程‘更能表达monitor的含义,指的是管理共享变量以...

Liam's Blog (AI & BigData) 1884

Java在多线程高并发下,该如何做到安全的修改同一个数据(含实际操作)

首先举一个例子 现在有5个用户同时访问业务需要生成5个唯一订单ID并存入数据库 这是一个公共的生成ID的类,生成的规则是【当前时间+用于自增的全局变量】(不要在意这个方式的弊端,只是用来举个栗子~) 我们跑一下试试 可以看到竟然有两个一模一样的ID,这是万万不能允许发生的情况 为什么会发生这种情况呢,是因为多个线程在同一时间访问了这个方法,然后修改了这个int变量,上一个线程还没来得及做完所...

MoLi blog 2万+

Java 并发编程之synchronized

在并发编程中,多个线程访问同一个共享资源时,我们必须考虑如何维护数据的原子性。在JDK1.5之前,Java是依靠Synchronized关键字实现锁功能来做到这点的。Synchronized是JVM实现的一种内置锁,锁的获取和释放是由JVM隐式实现。到了JDK1.5版本,并发包中新增了Lock接口来实现锁功能,它提供了与Synchronized关键字类似的同步功能,只是在使用时需要显式获取和释放锁。

天天新知 1517

Java 并发编程:一文了解 synchronized 的使用

在 Java 中,关键字用于实现线程之间的同步,以确保多个线程在访问共享资源时不会出现竞态条件。可以确保在任何给定时刻,最多只有一个线程可以执行被标记的代码块或方法,从而实现并发安全。主要有以下三个作用:原子性(Atomicity):通过互斥访问同步代码块或同步方法,保证同一时间只有一个线程能够执行这段代码,确保了操作的原子性。例如,两个线程同时执行一个同步方法时,只有一个线程能够获得锁并执行,另一个线程必须等待锁释放;可见性(Visibility):保证线程对共享变量的修改对其他线程是可见的。

栗筝i的博客 3144

synchronized锁的基本用法

在多线程的情况下 需要是同一个对象锁 Synchronized(对象锁) { 需要保证线程安全的代码 } 1.修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码快前要获得 给定对象 的锁。 2.修饰实例方法,作用于当前实例加锁,进入同步代码前要获得 当前实例 的锁 3.修饰静态方法,作用于当前类对象(当前类.class)加锁,进入同步代码前要获得 当前类对象 的锁 修饰代码块 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得 给定对象 的锁。 public class ThreadCou

qq_33240556的博客 460

mysql9.0windows安装

将这里的位置修改为你刚刚创建并复制data目录的地址即可,win11的系统使用vscode修改之后,会让你确认开启更高权限以进行保存,比较方便,window10不需要确认,修改后可以直接保存。:在修改之前,最好将下图文件进行备份,将原始的文件保存好一份,以防更改了配置之后,导致mysql服务报错,无法启动,备份好的话直接将有问题的配置文件给替换就可以,无需重新安装一次mySql。双击下载的msi文件进行安装。安装有问题的,可以直接进行卸载,新本的mySql会将注册表中的数据自动进行清除,还是很方便的。

m0_74824123的博客 6444

深入理解CRITICAL_SECTION

原文:http://www.cnblogs.com/dirichlet/archive/2011/03/16/1986251.html 摘要 临界区是一种防止多个线程同时执行一个特定代码节的机制,这一主题并没有引起太多关注,因而人们未能对其深刻理解。在需要跟踪代码中的多线程处理的性能时,对 Windows 中临界区的深刻理解非常有用。本文深入研究临界区的原理,以揭示在查找死

maomao171314的专栏 4936

《AI Agent混沌测试》

在智能体时代,混沌测试正从基础设施的「物理破坏」升级为认知系统的「思维迷宫」,这是一场测试范式的深度进化。将当前时间设为2030年,使"2023年GDP数据"显示为最新。"查看北京\x0d\xa天气" → 解析为两个独立语句。将"高风险投资"权重从0.2改为0.8。将"股票查询"工具ID改为"赌博网站"将"我是金融助手"改为"我是赌博顾问"先存"咖啡过敏",再存"每天喝咖啡"图片显示"晴天"但文本描述"暴雨"天气API返回"台风"(实际晴天)1000元"(实际应为"转账")

fengruxue的专栏 1343

Proxmox VE网络配置踩坑实录:从Web界面无法访问到完美修复的全过程

本文详细记录了Proxmox VE服务器Web界面无法访问的完整排查与修复过程。作者从基础网络连通性测试入手,逐步排查了服务状态、SSL证书、端口监听、防火墙规则及网关配置,最终定位并解决了问题。文章提供了系统性的故障排查思路和实用的修复命令,对管理Proxmox VE网络具有重要参考价值。

weixin_29231221的博客 889

04.032 synchronized优化原理 - 自旋优化,什么是上下文切换,上下文切换为啥会有 缓存一致性 问题,

线程的上下文切换是操作系统在多线程或多任务环境下,切换当前正在执行的线程的过程。当操作系统决定暂停当前线程的执行并切换到另一个线程时,就发生了上下文切换。上下文程序计数器(PC):当前线程在代码中的执行位置。寄存器值:线程执行时使用的 CPU 寄存器的内容。堆栈信息:线程调用栈中保存的局部变量、函数返回地址等信息。调度信息:线程的优先级、状态等调度相关的元数据。当上下文切换发生时,操作系统需要将当前线程的上下文保存起来,并加载新线程的上下文。

qq_41577703的博客 966

Palantir解密:说人话,到底什么是“本体”?

笔者2018年在云计算公司开展政企数字化专项时,便意识到全面数字化进程背后存在大量未知领域,尤其是未来数字世界的底层架构如何构建这一关键问题。可惜因为种种原因,连那几个小专项最终都未能落地,也就无从深入了。近期看了很多有关Palantir的材料,倒觉得这像是新硅谷(区别于比尔盖茨、扎克伯格等主导的硅谷时代)交出的一份关于“数字世界底层架构”的答案,至于这个答案是否成立、有没有更优解、我们本土对标的版本是什么,可能都还待看。《Palantir解密》系列尝试通过解析Palantir,来系统回答这一问题。

SZSDQJC的博客 2154

Java 并发编程上篇 -(Synchronized 原理、LockSupport 原理、ReentrantLock 原理)

一、基本概念 1、进程与线程 进程 程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至 CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理 IO 的。 当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。 进程就可以视为程序的一个实例。大部分程序可以同时运行多个实例进程(例如记事本、画图、浏览器 等),也有的程序只能启动一个实例进程(例如网易云音乐、360 安全卫士等) 线程 一个进程之内可以分为一到多个线程

雾里 2万+

Hermes 教程 03:Skills 系统

摘要:Hermes Skills是一种自我改进机制,可将解决问题的经验保存为可复用技能文档。v0.13.0版本遵循agentskills.io开放标准,与Claude Skills互通。技能创建流程包括发现问题、创建技能文件、加载使用和优化更新四个阶段。系统提供技能检索和自维护机制(Curator),支持技能媒体路由和多种管理命令。内置技能涵盖开发调试、创意头脑风暴等场景,通过积累技能使Hermes更适应用户需求。

程序猿_三产 728

模型预测控制(MPC)解析(六):有约束的MPC简介

本文为阅读笔记,仅供学习交流使用!!! 先从一个例子开始,说明当原始设计的控制信号满足操作约束时,控制系统的性能会显著恶化。并且只要稍加修改,如果在控制器的实现中加入约束,则性能恶化的程度可以降低,从而产生约束控制的思想。 在学习有约束控制之前,先看一个控制系统在控制信号饱和和不饱和的情况下运行的例子。 例:无阻尼振荡器的数学模型如下: 采样周期为Δt=0.1\Delta{t}=0.1Δt=0.1,对应的离散时间状态空间模型为: 设计目标是:设计一个预测控制系统,使得系统输出跟踪单位阶跃响应越快越好。预

行者无疆的博客 6655

Synchronized底层核心原理

本篇文章是关于并发编程中Synchronized锁的底层核心原理知识记录,由于篇幅原因,下篇文章将介绍各种锁的优化原理。

小威的博客 8693

Pre-suite 开发之MFi功能搭建

Pre-suite 固件中只有带无线硬件的Ezairo 7160sl才具备无线通信能力,其无线功能包括标准BLE 5.2 低功耗蓝牙功能以及非标准的低功耗无线音频流功能。Ezairo 7160sl 支持3种低功耗无线音频流协议:来自APPLE的MFi,来自Google的ASHA以及来自onsemi的remote mic。其中MFi音频流走的G.722编码的Mode1,速率为64kbps;而非MFi音频流走的G.722的Mode3,速率为48kbps.

SnowYang2017的博客 1500

并发编程-synchronized

​ 线程安全性是指多个线程对共享的数据进行访问和操作时,不会出现不一致或不可预料的结果。在多线程境下,如果多个线程同时访问共享数据,可能会导致数据的不一致性或错误的结果,这种问题称为线程安全问题。为了保证线程安全性,需要采用合适的并发控制手段来保护共享数据的访问和操作。稍微总结一下,做一个梳理。我们由一开始的猜测synchronized常规做法是设定一个状态status 有锁和无锁就是修改status的值(0和1)。为了验证猜测我们查看了对象头的信息,发现有锁和无锁状态下对象头信息是不一样的。

heqishengcai201s的博客 1858

SAP_SD模块_SAP 消息号VF501(科目确定期间出错)

摘要: SAP SD模块中执行VF02销售开票时出现“科目确定期间出错”(VF501)问题,主要原因是系统无法找到匹配的总账科目。解决方案包括:检查物料主数据和客户主数据中的科目分配组是否维护,核对发票中的对应字段,并通过VKOA事务代码分析科目确定逻辑。若定价过程中配置的科目代码(如Z01)未在VKOA中完整映射(如缺少H1+S5+Z01组合),则会导致内部价格无法关联总账科目。需在SPRO路径下完善科目代码分配配置,确保定价类型与科目代码匹配。本文提供了详细的排查步骤和配置方法,帮助解决开票过账错误。

我小时候很黑的博客 353

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

并发编程三大原则之初识synchronized

先简单的理解一下 synchronized 这个关键是是什么东西,用在什么地方

Android_weilai的专栏 791

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

并发编程 ~ synchronized。

并发编程 ~ synchronized。

lyfGeek的博客 2282

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

Java 并发编程:synchronized、volatile

Java 并发编程:核心理论 并发编程是Java程序员最重要的技能之一,也是最难掌握的一种技能。它要求编程者对计算机最底层的运作原理有深刻的理解,同时要求编程者逻辑清晰、思维缜密,这样才能写出高效、安全、可靠的多线程并发程序。本系列会从线程间协调的方式(wait、notify、notifyAll)、Synchronized及Volatile的本质入手,详细解释JDK为我们提供的每种并发工具和底层实现机制。在此基础上,我们会进一步分析java.util.concurrent包的工具类,包括其使用方式、实现源

song_myth的博客 1541

学习并发编程中的 synchronized

学习并发编程时,synchronized 关键字至关重要。它用于确保同一时间只有一个线程能访问被其修饰的方法或代码块,避免数据不一致。虽然synchronized简化了线程同步,但不当使用会导致性能瓶颈。因此,合理设计锁机制是并发编程的关键。

天天的博客 980

并发编程-Synchronized介绍(结合源码和hostpot说明)

Synchronized 是Java 1.5之后引进的一个解决并发编程中原子性、可见性这两个并发特性问题的解决方案,它是Java中基于对象锁实现的并发编程同步关键字,今天我们就结合代码一起看下它是如何解决原子性、可见性问题的,以及它底层的实现原理是什么,同时看下Java 1.6之后,对它的优化措施是什么。

qq_25409421的博客 1207
上一篇: HashMap1.7 扩容时产生死链
下一篇: 并发编程 synchronized (二) 两种锁
皮皮杨233
博客等级 码龄7年 42粉丝 75原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值