IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Java多线程 : 细说 synchronized -> 正文阅读

[Java知识库]Java多线程 : 细说 synchronized


theme: awesome-green
highlight: a11y-dark

总文档 :文章目录

Github : https://github.com/black-ant

1.1 synchronized 简述

synchronized 是一种重量级锁 , 可以保证在同一个时刻,只有一个线程可以执行某个方法或者某个代码块 .

  • 主要操作对象是方法或者代码块中存在的共享数据, 同时可保证一个线程的变化(主要是共享数据的变化)被其他线程所看

  • synchronized 的核心原理为 Java 对象头 以及 Monitor

1.2 Java 对象头 和 Monitor

// 原理
1 Java 对象头 和 Monitor
	|-> 对象头 :Mark Word(标记字段)、Klass Pointer(类型指针)
		|-> Klass Pointer : 类元数据指针,决定是何数据
		|-> Mark Word : 自身运行时数据 (hashcode,锁状态,偏向,标志位等)
	|-> Monitor : 
		|-> 互斥 :一个 Monitor 锁在同一时刻只能被一个线程占用

// Mark Word 和 Class Metadata Address 组成结构
--------------------------------------------------------------------------------------------------    
虚拟机位数	头对象结构	            说明
|---------|-----------------------|---------------------------------------------------------------|  
32/64bit	Mark Word	            存储对象的hashCode、锁信息或分代年龄或GC标志等信息
32/64bit	Class Metadata Address	类型指针指向对象的类元数据,JVM通过这个指针确定该对象是哪个类的实例。
--------------------------------------------------------------------------------------------------  

// 32位JVM的Mark Word默认存储结构
--------------------------------------------------------------------------------------------------           
  锁状态	       25bit	            4bit	         1bit是否是偏向锁	      2bit 锁标志位
|--------|--------------------|--------------------|----------------------|----------------------|  
 无锁状态	  对象HashCode	       对象分代年龄	            0	                    01
--------------------------------------------------------------------------------------------------    
            

// Monitor 的实现方式 @ https://blog.csdn.net/javazejian/article/details/70768369
ObjectMonitor中有两个队列以及一个区域
     _WaitSet 和 _EntryList,用来保存ObjectWaiter对象列表( 每个等待锁的线程都会被封装成ObjectWaiter对象) 
     _owner (指向持有ObjectMonitor对象的线程) 区域

  • 1 当多个线程同时访问一段同步代码时,首先会进入 _EntryList 集合, 此时开始尝试获取monitor
  • 2 当线程获取到对象的monitor 后进入 _Owner 区域 ,并把monitor中的owner变量设置为当前线程同时monitor中的计数器count加1
  • 3 若线程调用 wait() 方法,将释放当前持有的monitor,owner变量恢复为null,count自减1,同时该线程进入 WaitSet集合中等待被唤醒。
  • 4 若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor(锁)

// synchronized 源码分析 @ https://blog.csdn.net/javazejian/article/details/70768369  
// 具体的流程可以参考上面博客的具体分析, 此处仅总结 , 大佬们肝真好


// synchronized 代码块底层逻辑 ( monitorenter 和 monitorexit )
> synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,
其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置

Start : 当执行monitorenter指令时,当前线程将试图获取 objectref(即对象锁) 所对应的 monitor 的持有权
Thread-1  : objectref.monitor = 0 --> 获取monitor --> 设置计数器值为 1 
Thread-2  : 发现objectref.monitor = 0 --> 阻塞等待 --> Thread-1 执行 monitorexit ,
            计数器归 0 --> Thread-2 正常流程获取获取monitor

注意点 : 
编译器将会确保无论方法通过何种方式完成,方法中调用过的每条 monitorenter 指令都有执行其对应 monitorexit 指令 , 
方法异常时通过异常处理器处理异常结束
    
    
    

synchronized 方法底层逻辑 (ACC_SYNCHRONIZED标识)

  • 方法级的同步是隐式,即无需通过字节码指令来控制的,它实现在方法调用和返回操作之中。
  • JVM可以从方法常量池中的方法表结构(method_info Structure) 中的 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法。
  1. 方法调用时,调用指令将会 检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置
    |- 如果设置了,执行线程将先持有monitor(虚拟机规范中用的是管程一词), 然后再执行方法,最后再方法完成(无论是正常完成还是非正常完成)时释放monitor。
  2. 在方法执行期间,执行线程持有了monitor,其他任何线程都无法再获得同一个monitor。
    Error : 如果一个同步方法执行期间抛 出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的monitor将在异常抛到同步方法之外时自动释放

synchronized 内存级原理

// 最后生成的汇编语言 
lock cmpxchg %r15, 0x16(%r10)  和 lock cmpxchg %r10, (%r11)

  • synchronized的底层操作含义是先对对象头的锁标志位用lock cmpxchg的方式设置成“锁住“状态
  • 释放锁时,在用lock cmpxchg的方式修改对象头的锁标志位为”释放“状态,写操作都立刻写回主内存。
  • JVM会进一步对synchronized时CAS失败的那些线程进行阻塞操作,这部分的逻辑没有体现在lock cmpxchg指令上,我猜想是通过某种信号量来实现的。
  • lock cmpxchg指令前者保证了可见性和防止重排序,后者保证了操作的原子性。

1.3 synchronized 用法

// 加锁方式 ,当前实例 ,当前class , 自定义object
> synchronized(this)
> synchronized(object)
> synchronized(class) 或者静态代码块
            

synchronized关键字最主要的三种使用方式:

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

  • 修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁 。

    • 也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例对象,是类成员( static 表明这是该类的一个静态资源,不管new了多少个对象,只有一份,所以对该类的所有对象都加了锁)。
    • 所以如果一个线程A调用一个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态 synchronized 方法占用的锁是当前类的锁,而访问非静态 synchronized 方法占用的锁是当前实例对象锁。
  • 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

    • 和 synchronized 方法一样,synchronized(this)代码块也是锁定当前对象的。
    • synchronized 关键字加到 static 静态方法和 synchronized(class)代码块上都是是给 Class 类上锁。

    这里再提一下:synchronized关键字加到非 static 静态方法上是给对象实例上锁。
    另外需要注意的是:尽量不要使用 synchronized(String a), 部分字符串常量会缓冲到常量池里面, 不过可以试试 new String(“a”)

1.4 synchronized 其他知识点

 // 解释
1 synchronized 提供了一种独占式的加锁方式 ,其添加和释放锁的方式由JVM实现


// --> 阻塞synchronized  尝试获取锁的时候,获取不到锁,将会一直阻塞
            
            
>  谈谈 synchronized和ReenTrantLock 的区别
	- 两者都是可重入锁
	- synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API
    - ReenTrantLock 比 synchronized 增加了一些高级功能  
		?- 等待可中断;可实现公平锁;可实现选择性通知(锁可以绑定多个条件)
    
    
    

synchronized 与等待唤醒机制 (notify/notifyAll和wait)
等待唤醒机制需要处于synchronized代码块或者synchronized方法中 , 调用这几个方法前必须拿到当前对象的监视器monitor对象

synchronized 与 线程中断
线程的中断操作对于正在等待获取的锁对象的synchronized方法或者代码块并不起作用

1.5 多线程中的锁概念

1.5.1 锁按照等级分类

锁可以按照以下等级进行升级 : 偏向锁 -> 轻量级锁 -> 重量级锁 , 锁的升级是单向的

  • 偏向锁

    • 减少同一线程获取锁的代价 (在大多数情 况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得)
    • 如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word 的结构也变为偏向锁结构,当这个线程再次请求锁时,无需再做任何同步操作,即获取锁的过程
    • 偏向锁失败后,并不会立即膨胀为重量级锁,而是先升级为轻量级锁
  • 轻量级锁

    • 对绝大部分的锁,在整个同步周期内都不存在竞争
    • 轻量级锁所适应的场景是线程交替执行同步块的场合
    • 如果存在同一时间访问同一锁的场合,就会导致轻量级锁膨胀为重量级锁
  • 自旋锁

    • 自旋锁会假设在不久将来,当前的线程可以获得锁,因此虚拟机会让当前想要获取锁的线程做几个空循环
    • 在经过若干次循环后,如果得到锁,就顺利进入临界区。如果还不能获得锁,那就会将线程在操作系统层面挂起
    • 减少了线程上下文切换,但是增加了CPU消耗
  • 重量级锁

1.5.2 锁的操作

  • 清除锁 :
    • Java虚拟机在JIT编译时(可以简单理解为当某段代码即将第一次被执行时进行编译,又称即时编译),通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,通过这种方式消除没有必要的锁,可以节省毫无意义的请求锁时间

// Java 有三把主要锁
synchronized 关键字
ReentrantLock 重入锁
ReadWriteLock 读写锁


// 锁的类型
	? 自旋锁
		○ 适应自旋锁
	? 锁消除
	? 锁粗化
	? 锁的升级
		○ 重量级锁
		○ 轻量级锁
		○ 偏向锁
    


	
	

1.5.3 其他锁概念

  • 内部锁 :

    • synchronized : 锁对象的引用 , 锁保护的代码块
    • 每个Java 对象都可以隐式地扮演一个用于同步的锁的角色 ,这些内置的锁被称为 内部锁 或 监视器锁 .
  • 公平锁/非公平锁

    • 公平锁是指多线程按照申请锁的顺序来获取锁,非公平锁指多个线程获取锁的顺序不是按照申请锁的顺序,有可能造成优先级反转或者饥饿现象,
    • 非公平锁的优点在于吞吐量比公平锁大,ReentrantLock默认非公平锁,可通过构造函数选择公平锁,Synchronized是非公平锁。

  • 可重入锁
    • 可重入锁指在一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁,ReentrantLock与Synchronized都是可重入的。

  • 独享锁/共享锁
    • 独享锁是指一个锁只能一个线程独有,共享锁指一个锁可被多个线程共享,对于ReadWriteLock,读锁是共享锁,写锁是独享所。

  • 互斥锁/读写锁
    • 独享锁/共享锁是一种广义的说法,互斥锁/读写锁是其具体实现。

  • 乐观锁/悲观锁
    • 乐观锁与悲观锁是看待同步的角度不同,乐观锁认为对于同一个数据的修改操作,是不会有竞争的,会尝试更新,如果失败,不断重试。
    • 悲观锁与此相反,直接获取锁,之后再操作,最后释放锁。

  • 分段锁
    • 分段锁是一种设计思想,通过将一个整体分割成小块,在每个小块上加锁,提高并发。

1.6 锁的转换过程

对象头的变化可以看下图 , 说的很清楚了  @ https://www.cnblogs.com/jhxxb/p/10983788.html

// 之前知道 , 锁的状态改变是单向的 , 由 偏向锁 -> 轻量级锁 -> 重量级锁 ,我们分别捋一下

// 偏向锁 -> 偏向锁, 即重偏向操作
1 对象先偏向于某个线程, 执行完同步代码后 , 进入安全点时,若需要重偏向,会把类对象中 epoch 值增加
2 退出安全点后 , 当有线程需要尝试获取偏向锁时, 直接检查类实例对象中存储的 epoch 值与类对象中存储的 epoch 值是否相等, 如果不相等, 则说明该对象的偏向锁已经无效了, 可以尝试对此对象重新进行偏向操作。

// 偏向锁 -> 轻量级锁 
1 当发现对象已被锁定 ,且 ThreadID 不是自己 , 转为 偏向锁 , 在该线程的栈帧中建立 Lock Record 空间

lock_satus.jpg

1.7 为什么锁会转换

// 这要从机制说起 , 每一种锁都有各自的特点

> 偏向锁
- 优点 : 无 CAS ,消耗少 , 性能高 ,  可重入
- 缺点 : 锁竞争时撤销锁消耗高
- 场景 : 同一个线程执行同步代码

> 轻量级锁
- 优点 : 竞争的线程不会阻塞
- 缺点 : 轻量级锁未获取锁时会通过自旋获取 , 消耗资源
- 场景 : 线程交替执行同步块或者同步方法,追求响应时间,锁占用时间很短

> 重量级锁
- 优点 : 线程竞争不使用自旋 , 只会唤醒和等待
- 缺点 : 造成线程阻塞 , 锁的改变也消耗资源
- 场景 : 追求吞吐量,锁占用时间较长



锁状态pg.jpg

1.8 Synchoized 源码


synchronized 是一个修饰符 , 我们需要从 C 的角度去看 


Step 1 : 下载 OpenJDK 代码 https://blog.csdn.net/leisure_life/article/details/108367675
Step 2 : 根据代码索引 .c 文件



// TODO


1.9 Synchoized 用法



public void operation(Integer check) {

        //  案例一 : 校验无锁情况
//        functionShow(check);

        // 案例二 : 校验 synchronized 方法
        functionShowSynchronized(check);

        // 案例三 : 校验 synchronized 代码块
//        statementShowSynchronized(check);

        // 案例四 : 校验 代码块以 Class 为对象
//        classShowSynchronized(check);

        // 案例五 : 同步代码块 Object
//        objectShowSynchronized(check);

        // 案例五 : 同步代码块 Object
//        objectStringShowSynchronized(check);
    }

    /**
     *
     */
    public void functionShow(Integer check) {
        logger.info("------> check is {} <-------", check);
        if (check == 0) {
            showNum = 100;
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } else if (check == 1) {
            showNum = 200;
        }
        logger.info("------> check is Over {} :{}", check, showNum);
    }

    /**
     * 同步方法
     */
    synchronized public void functionShowSynchronized(Integer check) {
        logger.info("------> check is {} <-------", check);
        if (check == 0) {
            showNum = 100;
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } else if (check == 1) {
            showNum = 200;
        }
        logger.info("------> check is Over synchronized {} :{}", check, showNum);
    }

    /**
     *
     */
    public void statementShowSynchronized(Integer check) {
        logger.info("------> check is {} <-------", check);
        synchronized (this) {
            if (check == 0) {
                showNum = 100;
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else if (check == 1) {
                showNum = 200;
            }
        }
        logger.info("------> check is Over synchronized {} :{}", check, showNum);
    }

    public void classShowSynchronized(Integer check) {
        logger.info("check is {} <-------", check);
        synchronized (CommonTO.class) {
            if (check == 0) {
                showNum = 100;
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else if (check == 1) {
                showNum = 200;
            }
        }
        logger.info("check is Over synchronized {} :{}", check, showNum);
    }

    public void objectShowSynchronized(Integer check) {
        logger.info("check is {} <-------", check);
        synchronized (lock) {
            if (check == 0) {
                showNum = 100;
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else if (check == 1) {
                showNum = 200;
            }
        }
        logger.info("check is Over synchronized {} :{}", check, showNum);
    }

    public void objectStringShowSynchronized(Integer check) {
        logger.info("check is {} <-------", check);
        synchronized (lock2) {
            if (check == 0) {
                showNum = 100;
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else if (check == 1) {
                showNum = 200;
            }
        }
        logger.info("check is Over synchronized {} :{}", check, showNum);
    }
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-07-26 11:56:40  更:2021-07-26 11:57:26 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年4日历 -2024/4/23 17:17:09-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码