并发编程笔记2_synchronized使用和注意事项

简介: 并发编程笔记2_synchronized使用和注意事项

前言:

上一篇学习了并发bug的成因,我们知道当一个线程的时间片使用完的时候,操作系统就会切换到另外一个线程,如果这两个线程访问了相同的资源,可能就会导致并发问题。
我们可以想到如果这个共享的资源一次只能一个线程访问,其他线程不能访问的话,就不会因为切换线程而产生的问题了。java并发编程中就提供了这样的机制,互斥锁来保证一次只有一个线程能访问共享的资源。
java中有synchroized和lock,今天我们先来看下synchroized关键字。

使用方法:

synchroized分为修饰方法和代码块:
修饰方法:

// 修饰方法  锁为对象
public synchronized void method1() {
       // 处理过程
   }

// 修饰静态方法 锁是类
public synchronized static void method2() {
       // 处理过程
   }

修饰代码块:

// 修饰代码块 锁为对象
public void method3() {
      synchronized (this) {
          // 处理过程
      }
  }

// 修饰代码块 锁为类
  public void method4() {
      synchronized (Test.class) {
          // 处理过程
      }
  }

我们来看一个经典的例子对个线程对一个共享变量进行加法

public class Test {

    private int i = 0;

    public void add() {  // (1)
        i++;
    }

    public static void main(String[] args) throws InterruptedException {
        Test test = new Test();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                test.add();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                test.add();
            }
        });
        t1.start();
        t2.start();

        t1.join();
        t2.join();
        System.out.println("i的值是" + test.i); // (2) i的值是18084
    }
}

预计的结果是20000,计算出来的值是小于20000的,线程t1和线程t2几乎同时启动,各自会将i的值读入到自己的缓存中,因为一个线程时间片用完了,操作系统会切换到另一个线程,
此时线程t1缓存中的值还没有刷新到内存中,线程t2对i增加是自己缓存中的值,然后t2增加完以后在将i增加后的值,刷到内存中。t1相加的操作等于没做,所以最终的i的值会小于20000.

当在(1)处加上synchroized的关键字后,变成:
public synchronized void add() {
线程t1运行add()函数时,先进入临界区(就是被synchroized修饰的部分),获得到monitor的锁,此时如果进行了线程切换,另一个线程因为没有获得到锁,所以会被阻塞住,当时间片用完。
线程在被切换为t1时,可以继续执行。这样的同时只允许一个线程操作的互斥锁就保证了共享变量不会出现问题。

可重入锁

可重入性:就是当一个线程获得锁以后,此线程可以重复获得锁,并且不会阻塞。比如下面的例子如果线程1调用add1已经获取了锁,在调用add2又获得了锁,线程1还是可以正常运行的,这就是可重入锁,
java的另一种锁ReentrantLock(可重入锁也是一样的效果)

public synchronized void add1() {
              add()2;
          }

public synchronized void add2() {
            // 处理内容
        }

synchronized的实现

我们下面的Test类进行javac Test.java编译 , 在对class文件进行反编译javap -v Test,查看下附加的信息

public class Test {
    public synchronized void method1(){
    }

    public void method2(){
        synchronized (Test.class){
        }
    }
}

查看信息

public synchronized void method1();
   descriptor: ()V
   flags: ACC_PUBLIC, ACC_SYNCHRONIZED
   Code:
     stack=0, locals=1, args_size=1
        0: return
     LineNumberTable:
       line 12: 0

 public void method2();
   descriptor: ()V
   flags: ACC_PUBLIC
   Code:
     stack=2, locals=3, args_size=1
        0: ldc           #2                  // class com/algorithm/leetcode/leetcode/Test
        2: dup
        3: astore_1
        4: monitorenter
        5: aload_1
        6: monitorexit
        7: goto          15
       10: astore_2
       11: aload_1
       12: monitorexit
       13: aload_2
       14: athrow
       15: return

可以看到java中实现同步方法是使用的ACC_SYNCHRONIZED控制的,实现同步代码块是使用monitorenter和monitorexit来控制的,monitorenter表示进入临界区,monitorexit表示从临界区退出来。
synchroninzed是由monitor对象控制的,任意一个java对象都可以成为monitor对象(可以简单的理解为synchroized的锁),它里面保存着拥有该锁的线程的信息,从这信息可以知道是哪个线程正持有这个锁。
在jvm中的对象是由实例变量,对象头,填充数据等组成,而monitor就保存在对象头里面,对象头的MarkWord中的LockWord指向monitor的起始地址,这样就知道是哪个线程持有着锁。

注意事项:

下面是java并发编程实战课程里面的一个题目:


class SafeCalc {
  long value = 0L;
  long get() {
    synchronized (new Object()) {
      return value;
    }
  }
  void addOne() {
    synchronized (new Object()) {
      value += 1;
    }
  }
}

这样使用synchroized是不生效的哦,看完上面的内容我们知道锁的地址是保存在对象头里面的,new了两个对象,那么就是两个不同锁,所以不会起作用。

参考资料:

1.https://time.geekbang.org/column/intro/159 极客时间并发编程实战
2.《java并发编程实战》 Doug lea
3.http://www.hollischuang.com/archives/1883 Synchronized的实现原理
4.https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.11.10
5.https://blog.csdn.net/sc9018181134/article/details/80643360 (klass对象 我的另一个博客)

相关文章
|
JavaScript 前端开发 .NET
ASP编程中的"Microsoft JScript 编译错误 错误 &#39;800a03ec&#39;缺少;"的解决方法.
解决方法: 试: “组件服务”,在组件服务中找到Excel进行配置; 给everyone,network控制权限,Microsoft Excel Application DCOM 属性中标识->选择“交互式用户”->确定。
1165 0
|
4天前
|
云安全 人工智能 运维
当攻击全面Agent化,你的安全防御能否跟上AI Speed?
多起客户侧AK泄露事件应急的真实观察
1231 3
|
27天前
|
人工智能 JSON API
全网刷屏的 Jev 模型正式开放!一手实战测评 + 保姆级教程
全网爆火的 Jev 模型是什么?有什么用?怎么使用?怎么接入 AI 编程工具?效果真的好么?傻子可懂的 Jev 保姆级实战教程 + 项目实战测评来啦
9694 32
|
13天前
|
存储 人工智能 并行计算
大模型本地部署终端选型方法论:以 Qwen3.8-27B 为例的四档分层完整流程
本文提出一套大模型本地部署终端选型方法论:定约束、定档位、定框架、定参数四步决策法,配合入门、主力、质量、无损四档分层模型。以 Qwen3.8-27B 实测数据为例,逐环节解读显存、带宽、存储、散热、系统、预算等要素,给出面向不同预算的优选方案、决策自查清单与市场观察框架。文末前瞻 AI 笔记本的 CPU+GPU 与统一内存两条路线,论证四步决策法在新品类上的延续性。
|
7天前
|
存储 人工智能 安全
千问办公官网入口网页版登录:qwenwork.cn 注册送2000积分、登录送100积分
阿里云千问办公(QwenWork)是AI智能工作平台,支持一句话生成PPT、表格、网页、视频及数据分析。个人免费版送2000积分+每日登录赠100分,含1GB存储、5个发布页;企业版198元/席/月起。阿里千问办公QwenWork官网:https://t.aliyun.com/U/0VCTGt 阿里AI工作平台,一句话完成数据分析、PPT 生成、视频剪辑、网页搭建等复杂任务
|
12天前
|
人工智能 JSON 自然语言处理
2026 年 Jev 决策模型深度拆解:原理解读、实战测评与保姆级落地教程
有一款特殊AI模型在开发者圈子刷屏,它摒弃传统大模型擅长的对话聊天能力,专注做高速结构化决策,它就是TypeSafe AI推出的Jev模型。该模型由ChatGPT共同发明人Diogo Almeida主导研发,定位为**System One Model(系统一模型)**,对标人类大脑快速直觉判断的思维模式,在响应延迟、调用成本、结构化输出稳定性上相比传统生成式大模型有着巨大差异。本文会完整拆解Jev底层原理、三大核心原语能力、适用业务场景,同时提供可直接运行的curl、Python代码示例,并且结合多组实测数据,客观分析模型优势与能力边界,帮助普通开发者和AI应用从业者快速上手落地。
857 1
|
4天前
|
Web App开发 人工智能 JavaScript
太猛了,一个月干到15W+,给大家推荐一个最近爆火的开源逆向工具:REA
REA(Reverse Engineer Anything)是近期爆火的开源AI逆向工具,4月发布,1个月内斩获15万+ Star。它让AI Agent无需源码即可分析二进制、APK、JS/Electron、HAR包等17类目标,自动反编译、生成可运行代码,并强制返回带证据与局限的结论。全本地执行,MIT协议,支持Hopper/Ghidra/IDA等引擎,一行命令即可上手。
552 1
太猛了,一个月干到15W+,给大家推荐一个最近爆火的开源逆向工具:REA
|
8天前
|
人工智能 JSON 自然语言处理
2026 年 Jev 决策模型全解析:核心原理拆解,性能实战测评与落地部署教程
有一款特殊AI模型在开发者圈子刷屏,它摒弃传统大模型擅长的对话聊天能力,专注做高速结构化决策,它就是TypeSafe AI推出的Jev模型。该模型由ChatGPT共同发明人Diogo Almeida主导研发,定位为System One Model(系统一模型),对标人类大脑快速直觉判断的思维模式,在响应延迟、调用成本、结构化输出稳定性上相比传统生成式大模型有着巨大差异。本文会完整拆解Jev底层原理、三大核心原语能力、适用业务场景,同时提供可直接运行的curl、Python代码示例,并且结合多组实测数据,客观分析模型优势与能力边界,帮助普通开发者和AI应用从业者快速上手落地。
561 0