并发编程 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

ARM Cortex-M系列授权解析:从研发到量产的免费与付费之路

本文深入解析了ARM Cortex-M系列内核从研发到量产过程中的授权模式。研发阶段可利用ARM DesignStart等免费资源进行原型验证,但产品商业化量产则需支付授权费与版税。文章对比了采购商用MCU与自研芯片的成本差异,并探讨了RISC-V作为免费替代方案在IoT领域的应用前景与挑战,为开发者提供了清晰的选型指南。

weixin_29292431的博客 494

深入理解CRITICAL_SECTION

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

maomao171314的专栏 4936

ICC2 高效上手 | create_lib 命令全攻略:数字后端工程师的必备秘籍

如果技术文件中的足够满足你的需求,你可以设置,然后运行create_lib命令;如果技术文件的精度不够,那就需要显式地使用来定义缩放因子。在使用create_lib必须显式指定库名(如my_libA推荐优先通过或来关联技术数据,确保你的设计库有正确的工艺基础。显式地通过-ref_libs定义参考库,这样可以避免工具自动推导时可能出现的冲突。慎重使用,只有在对数据精度有特殊需求时才显式指定,并且要确保它与参考库的缩放因子一致。

weixin_50820814的博客 1595

SpringBoot+Vue前后端分离项目实战:个人博客系统

MyBlog主要是基于 SpringBoot + Vue 前后端分离开发的一款动态个人博客系统,后台和前台均通过JSON数据做交互,主要功能有文章管理、分类管理、标签管理、附件管理、评论管理、友链管理、个人资料/博客信息管理等。

Myles'Blog 1万+

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

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

qq_41577703的博客 966

LLVM实战:如何用Clang+LLVM打造你的第一个C++编译器(附完整配置步骤)

本文详细介绍了如何使用Clang+LLVM构建C++编译器的完整配置步骤,从环境准备到编译流程剖析,再到调试与优化技巧。通过实战指南,帮助开发者深入理解LLVM编译器框架的工作原理,并掌握交叉编译与性能分析等高级功能。

sprite的博客 746

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

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

雾里 2万+

Axial Attention 和 Criss-Cross Attention及其代码实现

Axial Attention 和 Criss-Cross Attention及其代码实现 ​ 文章目录Axial Attention 和 Criss-Cross Attention及其代码实现1 Criss - Cross Attention介绍1.1 引言1.2 理论实现1.2.1 获取权重A1.2.2 Affinity操作1.3.3 全部信息获取1.3 代码实现1.3.1 官方实现1.3.2 纯pytorch实现2 Axial Attention 介绍2.1 引言2.2 理论实现2.3 代码实现2.3

fzl的博客 2万+

【数据分析】宏基因组alpha和beta多样性分析和差异物种筛选案例

本教程为读者呈现了宏基因组α和β多样性分析的完整案例。通过实际数据和详细步骤,读者可以深入理解这两种分析方法及其在R语言中的实现过程。教程中不仅提供了清晰的代码示例,还对每一步骤进行了详细解释,使读者能够轻松跟随并掌握分析技巧。无论是初学者还是有一定基础的研究者,都能通过本教程快速上手宏基因组多样性分析,为微生物组学研究提供有力支持。

专注生信领域 2194

Synchronized底层核心原理

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

小威的博客 8693

逻辑判断1-逻辑基础知识

判断推理学习笔记

小y的学习笔记 2131

并发编程-synchronized

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

heqishengcai201s的博客 1858

IgH EtherCAT 从入门到精通:第 29 章 实战:EoE 远程维护通道搭建

摘要:本章详细介绍了基于EoE(Ethernet over EtherCAT)协议的远程维护通道搭建方案。主要内容包括:1)网络架构设计,采用管理网络与EoE子网隔离的方案;2)从站EoE支持确认与IP地址配置,提供API和命令行两种方式;3)路由/交换两种部署模式,重点推荐路由模式并给出自动化配置脚本;4)SSH远程访问配置,包括直接连接和跳板访问两种方式;5)安全加固措施,如防火墙规则、SSH密钥认证等;6)故障恢复机制与性能注意事项。该方案实现了通过EtherCAT网络对从站设备的远程SSH访问、文件

从事嵌入式linux android驱动开发,拥有全栈能力 360

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

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

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

Android_weilai的专栏 791

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

并发编程 ~ synchronized。

并发编程 ~ synchronized。

lyfGeek的博客 2282

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

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、付费专栏及课程。

余额充值