synchronized

线程锁

1.1synchronized的认识

1.1.1synchronized的介绍

在多线程并发编程中,synchronized关键字是重量级锁的代名词。但是,随着JDK的发展,对synchronized底层进行了各种优化后,有些情况它就并不那么重了,JDK 1.6中为了减少获取锁和释放锁带来的性能消耗而引入了偏向锁和轻量级锁的概念,以及锁
的存储结构和升级的过程。

1.1.2synchronized的使用

synchronized有三种加锁方式:

  • 修饰实例方法,作用于当前实例对象,进入同步代码前需要获得当前实例的锁
  • 修饰静态方法,作用于当前类对象,进入同步代码前要获得当前类对象的锁
  • 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码块之前要获得给定对象的锁
class SynchronizedDemo {
    // 类锁(修饰静态方法:锁当前类的 Class 对象。)
    public static synchronized void inStaticMethod() {
        for (int i = 0; i < 10; i++) {
            System.out.println("aaa");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    // 类锁(修饰代码块,锁括号中的 Class 对象)
    public static void inStaticMethodLockClassObj() {
        synchronized(SynchronizedDemo.class){
            for (int i = 0; i < 10; i++) {
                System.out.println("aaa");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    // 对象锁(修饰普通方法:锁当前实例对象)
    public synchronized void inNormalMethod() {
        for (int i = 0; i < 10; i++) {
            System.out.println("bbb");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    // 对象锁(修饰代码块:锁括号中的实例对象)
    public  void bb() {
        synchronized(this){
            for (int i = 0; i < 10; i++) {
                System.out.println("bbb");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    // 无锁
    public void cc() {
        for (int i = 0; i < 10; i++) {
            System.out.println("ccc");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

1.1.3synchronized关键字的原理

synchronized关键字的实现是依赖JVM的,需要了解几个概念。
1.对象头
在JVM的堆内存中,每个对象主要由三部分构成:对象头、实例变量、填充字节。
一般而言,synchronized使用的锁对象是存储在Java对象头里。它是轻量级锁和偏向锁的关键。

  • 对象头包括标记字段、类型指针
    标记字段(Mark Word):用于存储对象自身的运行数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向锁ID等等。
    一般占用两个机器码。在32-bit JVM上占用64bit,在64-bit JVM中占用128bit即16bytes。
    32位虚拟机上Mark Word的存储:
    synchronized
    64位虚拟机上Mark Word的存储:
    synchronized
  • 实例变量:存储对象的属性信息,包括父类的属性信息,按照4字节对齐
  • 填充字节:因为虚拟机要求对象字节必须是8字节的整数倍,填充字符就是用于凑齐这个整数倍
    2.Monitor对象
  • Monitor是一种用来实现同步的工具
  • 与每个JAVA对象关联,所有JAVA对象天生携带Monitor
  • Monitor是实现Sychronized的基础
    对象监视器(Monitor)由ObjectMonitor对象实现(c++),其跟同步相关的数据结构如下:
ObjectMonitor() {
    _count        = 0; //用来记录该对象被线程获取锁的次数
    _waiters      = 0;
    _recursions   = 0; //锁的重入次数
    _owner        = NULL; //指向持有ObjectMonitor对象的线程
    _WaitSet      = NULL; //处于wait状态的线程,会被加入到_WaitSet
    _WaitSetLock  = 0 ;
    _EntryList    = NULL ; //处于等待锁block状态的线程,会被加入到该列表
}

1.1.3synchronized锁升级

JDK1.6对synchronized加锁进行了优化,其优化的核心就是锁升级:
无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁
无锁状态:对象头中Mark Word的偏向锁标志位为0,默认后两个为01,标志该对象可进行偏向;

无锁 -> 偏向锁

当线程第一次进入synchronized关键字锁住代码块的时候,也就是锁对象第一次被线程获取时,锁对象进入偏向状态,同时使用CAS操作将线程ID记录到Mark Word中,偏向锁不会主动释放,所以当这个线程在此请求锁时,无需在重新获取锁。
所以偏向锁在对于没有锁竞争的情况下,偏向锁有很好的优化;

偏向锁 -> 轻量级锁

当有其他线程尝试竞争偏向锁时,则还是通过CAS操作竞争锁,这里并不会直接升级锁,原因是偏向锁不会主动释放,因此即使之前的线程执行完毕之后,该锁对象存储的还是上一个线程ID。具体操作是
会检测当前锁存储的线程ID是否还存活,如果没有存活,则将锁标志位置为无锁状态,线程即可通过CAS重复之前偏向锁的步骤;如果之前的线程仍然存活,则检查该线程的栈帧信息,如果需要继续持有这个锁对象,那么暂停该线程,撤销偏向锁,升级为轻量级锁,若不在使用该锁对象,继续上一步,置为无锁,进行CAS竞争偏向。

轻量级锁 -> 重量级锁

每个线程的栈帧都会包含一个锁记录 Lock Record的结构,内部可以存储锁对象的Mark Word,让锁记录Object reference指向锁对象,并尝试用CAS替换锁对象的对象头中的Mark Word替换为执行锁记录的指针,成功则当前线程获取锁,失败这又分为两种状态:1.表示其他线程获取当前锁对象,用自旋的方式获取锁,自旋获取锁失败则会膨胀为重量级锁。2.当前线程自己执行了锁重入,那么会在添加一条Lock Record重入锁的记录。
轻量级锁的目的是在多线程交替执行同步代码块时(未发生竞争),避免使用互斥锁(重量级锁)带来的性能消耗。这一种选择的想法是,短时间的自旋,换取线程在用户态和内核态之间切换的开销。

synchronized

重量级锁

重量级锁是通过锁对象内部的监视器(monitor)实现的。其中monitor的本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态到内核态的切换,切换成本非常高。线程竞争不使用自旋,不会消耗CPU。但是线程会进入阻塞等待被其他线程被唤醒,响应时间缓慢。
插一张网上很好的原理图:

synchronized

Original: https://www.cnblogs.com/amazing-yml/p/16194600.html
Author: amazing_yml
Title: synchronized

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/643810/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Log4j 日志框架

    Log4j(Log for java)是 Apache 的一个开源项目,通过使用 Log4j,可以控制日志信息输送的目的地是控制台或文件等,也可以控制每一条日志的输出格式。通过定义…

    技术杂谈 2023年7月11日
    074
  • Java中方法的定义和使用

    方法的定义和使用 注意事项: 1.方法与方法之间是 平级关系 不可以嵌套定义 2.方法的位置 可以在类{}中任意位置 3.方法定义之后 之后被调用 才能被执行 4.return 关…

    技术杂谈 2023年6月21日
    090
  • Win10系统的DELL平板如何重装WIN10系统

    首先参考”Win10系统的SurfacePro4如何重装系统-1 SurfacePro专用的PE”这篇文章,做一个WIN10平板专用的PE 然后开机按F2可…

    技术杂谈 2023年5月31日
    0103
  • 基于AudioQueue实现音频的录制和播放

    基于AudioQueue实现音频的录制和播放 @ 基于AudioQueue实现音频的录制和播放 背景 总览 Audio Queue 架构 AudioQueueBuffer数据结构 …

    技术杂谈 2023年7月25日
    085
  • 入门Python,看完这篇就行了!

    转载请注明出处❤️ 作者:测试蔡坨坨 原文链接:caituotuo.top/3bbc3146.html 你好,我是测试蔡坨坨。 众所周知,Python语法简洁、功能强大,通过简单的…

    技术杂谈 2023年7月11日
    0103
  • DirectUI消息循环的简单封装

    一.真窗体和假窗体 首先在DirectWindow内部创建一个真窗体(基于WTL),可以接收消息 class CMessageWindow : public CWindowImpl…

    技术杂谈 2023年5月31日
    0111
  • 需求分析-PIECES框架

    PIECES框架是IT项目系统需求分析时的一个模型。PIECES框架能够完整、准确、快速地确定信息系统的需求,确认业务中存在的问题、机会和改进目标。 A checklist for…

    技术杂谈 2023年6月1日
    0102
  • 出现The following packages have unmet dependencies问题【转】

    转自:https://blog.csdn.net/Davidietop/article/details/88934783 Ubuntu16.04+ROS Kinetic 问题背景和…

    技术杂谈 2023年5月31日
    0108
  • log4j 不同模块输出到不同的文件

    1、实现目标 不同业务的日志信息需要打印到不同的文件中,每天或者每个小时生成一个文件。如,注册的信息打印到register.log,每天凌晨生成一个register-年月日.log…

    技术杂谈 2023年5月31日
    0100
  • SpringMVC详解

    SpringMVC的介绍 【1】Spring Web MVC是基于Servlet API构建的原始Web框架,从一开始就已包含在Spring框架中。正式名称” Spri…

    技术杂谈 2023年7月24日
    089
  • 毫秒时间转换

    https://www.npmjs.com/package/ms 作者:孟繁贵 Email:meng010387@126.com 期待共同进步! Original: https:/…

    技术杂谈 2023年5月31日
    0104
  • Swift开发iOS应用过程中的问题和解决记录

    虚拟机里安装OSX+XCode开发环境 用真机的请直接跳过这个部分。 主要是在VitrualBox里安装mac系统和xcode,参考这篇教程,VirtualBox的版本是4.3.1…

    技术杂谈 2023年5月31日
    0127
  • Flink Time

    基础概念 支持三种时间概念: Processing Time 时间递增 Ingestion Time : 摄入时间,数据进入Flink框架的时间,在Source Operator中…

    技术杂谈 2023年7月10日
    063
  • 从 posix_spawn() 函数窥探漏洞逃逸

    posix_spawn() 函数是用来在Linux上创建子进程的,头文件是 #include <spawn.h></spawn.h> ,语法如下: #inc…

    技术杂谈 2023年5月31日
    099
  • 【翻译】WhatsApp 加密概述(技术白皮书)

    会话初始化设置 要与另一个 WhatsApp 用户通信,WhatsApp 客户端需要先建立一个加密会话。加密会话一旦被创建,客户端就不需要再重复创建会话,除非会话失效(例如重新安装…

    技术杂谈 2023年5月31日
    088
  • 五分钟搞懂POM设计模式

    大家好,我是测试蔡坨坨。 今天,我们来聊聊Web UI自动化测试中的POM设计模式。 前期,我们学会了使用Python+Selenium编写Web UI自动化测试线性脚本 线性脚本…

    技术杂谈 2023年7月11日
    074
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球