【并发编程八:线程安全问题分析及锁synchronized的介绍(1)】 灰太狼 2023-10-09 14:09 9阅读 0赞 **【衔接上一章 [【并发编程七:Java中的线程池(4)-线程池规范、监控、关闭】][Java_4_-]】** #### 学习路线 #### * * 2.1什么是线程安全? * 2.2线程不安全原因剖析 * 2.2.1原子性 * 2.2.2线程上下文切换 * 2.3synchronized解决线程安全 * 2.4synchronized同步锁的使用 * * 2.4.1synchronized使用语法 * 2.4.2synchronized同步锁分类 * 2.4.3对象锁 * * 1、两个线程同时访问同一个对象的同步方法,会互斥; * 2、两个线程同时访问同一个对象的同步方法和非同步方法,不互斥; * 3、两个线程同时访问两个对象的同步方法,不互斥; * 4、两个线程同时访问两个对象的同步方法和非同步方法,不互斥; * 2.4.4类锁 * * 1、两个线程同时访问同一个类的静态同步方法,会互斥; * 2、两个线程同时访问不同类的静态同步方法,不互斥; * 3、两个线程同时访问同一个类的静态同步方法和静态非同步方法,不互斥; * 4、两个线程同时访问同一个类的静态同步方法和非静态同步方法,不互斥; * 2.5synchronized底层实现原理剖析 * * 2.5.1JVM堆内存中的对象布局? * * 对象头(Header): * 实例数据(Instance Data): * 对齐填充(Padding): * 2.5.2JOL查看Java对象内存布局 ### 2.1什么是线程安全? ### * 白话一点,线程安全是指多个线程同时运行的时候,会不会产生预期之外的结果?比如我要对i=0的变量进行+1操作,1个线程执行的时候,结果是1,两个线程执行的时候,结果应该是2,但是结果却是1,那就产生了线程安全问题; * 产生线程安全问题是因为多个线程访问内存中的共享资源,从而产生了冲突、产生了脏数据、产生了意外的结果; * 所谓共享资源就是多个线程都可以访问的资源; ![在这里插入图片描述][3fad68bc2e284b94b5920e9ae67e6400.png] * Java代码运行在jvm中,jvm的公共内存是堆、元空间(方法区),同一个jvm进程都能访问这个公共内存,正因为这块内存是大家都可以访问的,是公共的,这样就会导致内存不安全的问题; * 所以线程安全指的是,**在共享内存中的公共数据由于可以被任何线程访问到,在没有限制的情况下存在被意外修改的风险**; * 所以对于像堆、元空间(方法区)这种公共区域,多个线程去操作时,是存在安全问题的; * 但是注意:**如果多个线程都只是读取共享资源而不去修改,那就不会存在线程安全,只有当至少有一个线程修改了共享资源时才会存在线程安全**; ### 2.2线程不安全原因剖析 ### 导致线程不安全的底层核心原因主要有三个: * 1、原子性 * 2、有序性 * 3、可见性 ### 2.2.1原子性 ### * 我们经常听到数据库中事务的原子性,数据库事务的原子性是指一组对数据库的操作,要么最终全部成功执行,要么最终全部回滚; * 多线程并发编程的原子性是指一组指令被执行时,不受其他指令的干扰,比如我们说CAS(Compare & Set)是原子的,java.util.concurrent.atomic下的原子操作类等,这里的原子性其实指的是隔离性,也就是一组操作不能被别的线程干扰; ### 2.2.2线程上下文切换 ### * 1、使用多线程的目的是为了充分利用多核CPU; * 2、当创建很多线程,CPU不够用了,此时就是一个CPU来应付多个线程; * 3、操作系统采用时间片切换的方式把每个线程分配给CPU去执行,线程在时间片内占用 CPU 执行任务,当线程使用完时间片后,就会处于就绪状态并让出 CPU从而让其它线程去使用,这就是上下文切换; ![在这里插入图片描述][3126c5bc5c554e16ac8b9d9fa69275c7.png] ![在这里插入图片描述][819c19b956194cd99f060d954ab0ebe5.png] ![在这里插入图片描述][1457cc896a7748a6a8cb275b617861da.png] ### 2.3synchronized解决线程安全 ### * 前面分析的线程安全问题可以采用synchronized来解决; * synchronized是 Java 中的一个关键字,它的使用就是在代码上加一个- - -synchronized关键字即可; * synchronized是Java的内置锁; * 这把锁我们看不见,所以也被称为内部锁或监视器锁,它的作用就是在同一时刻只有一个线程能进入synchronized代码块; * **加锁**:线程在进入 synchronized 代码块前会自动获取内部锁,此时其它线程访问该同步代码块时会被阻塞挂起; * **解锁**:拿到内部锁的线程在正常退出代码块、抛出异常后、在同步代码块内调用了该内置锁资源的wait系列方法时 释放该内置锁; synchronized内置锁是排它锁,因为当一个线程获取这个锁后,其他线程必须等待该线程释放锁后才能获取该锁; ### 2.4synchronized同步锁的使用 ### #### 2.4.1synchronized使用语法 #### * (1)作用在方法上; - 修饰非静态方法(普通方法) - 修饰静态方法 * (2)作用在代码块上;(粒度更细,更加灵活) * synchronized(this|object) \{…\} * synchronized(类.class) \{…\} /** * synchronized关键字使用语法 * 1、方法上 * 2、代码块上 * */ public class Test00 { public static void main(String[] args) { Test00 test00 = new Test00(); test00.test1(); test00.test2(); } public synchronized void test1() { System.out.println("test1......"); } public void test2() { synchronized(this) { System.out.println("test2......"); } } } #### 2.4.2synchronized同步锁分类 #### (1)对象锁; /** * 两个线程同时访问同一个对象的同步方法,会互斥; * */ public class Test01 { public static void main(String[] args) throws InterruptedException { Test01 test01 = new Test01(); new Thread(() -> test01.eat(), "t1").start(); new Thread(() -> test01.eat(), "t2").start(); } //对象锁 public synchronized void eat() { while (true) { System.out.println(Thread.currentThread().getName() + " 吃饭......" + System.currentTimeMillis()); ThreadUtils.sleepMilliSeconds(300); } } } /** * 两个线程同时访问同一个对象的同步方法和非同步方法,不互斥; */ public class Test02 { public static void main(String[] args) throws InterruptedException { Test02 t = new Test02(); new Thread(() -> t.eat(), "t1").start(); new Thread(() -> t.work(), "t2").start(); } //同步方法 public synchronized void eat() { while (true) { System.out.println(Thread.currentThread().getName() + " 吃饭......" + System.currentTimeMillis()); ThreadUtils.sleepMilliSeconds(300); } } //非同步方法 public void work() { while (true) { System.out.println(Thread.currentThread().getName() + " 工作......" + System.currentTimeMillis()); ThreadUtils.sleepMilliSeconds(300); } } } (2)类锁; public class Test05 { //new一个对象作为锁 private Object lock = new Object(); public static void main(String[] args) throws InterruptedException { new Thread(() -> eat(), "t1").start(); new Thread(() -> eat(), "t2").start(); } /* 两个线程同时访问同一个类的静态同步方法,会互斥; public synchronized static void eat() { while (true) { System.out.println(Thread.currentThread().getName() + " 吃饭......" + System.currentTimeMillis()); ThreadUtils.sleepMilliSeconds(300); } }*/ public static void eat() { synchronized (Test05.class) { //类锁 while (true) { System.out.println(Thread.currentThread().getName() + " 吃饭......" + System.currentTimeMillis()); ThreadUtils.sleepMilliSeconds(300); } } } public static void eat2() { synchronized (Test05.class.getClass()) { //类锁 while (true) { System.out.println(Thread.currentThread().getName() + " 吃饭......" + System.currentTimeMillis()); ThreadUtils.sleepMilliSeconds(300); } } } public void eat3() { synchronized (lock) { //对象锁 while (true) { System.out.println(Thread.currentThread().getName() + " 吃饭......" + System.currentTimeMillis()); ThreadUtils.sleepMilliSeconds(300); } } } } #### 2.4.3对象锁 #### ##### 1、两个线程同时访问同一个对象的同步方法,会互斥; ##### /** * 两个线程同时访问同一个对象的同步方法,会互斥; * */ public class Test01 { public static void main(String[] args) throws InterruptedException { Test01 test01 = new Test01(); new Thread(() -> test01.eat(), "t1").start(); new Thread(() -> test01.eat(), "t2").start(); } //对象锁 public synchronized void eat() { while (true) { System.out.println(Thread.currentThread().getName() + " 吃饭......" + System.currentTimeMillis()); ThreadUtils.sleepMilliSeconds(300); } } } ##### 2、两个线程同时访问同一个对象的同步方法和非同步方法,不互斥; ##### /** * 两个线程同时访问同一个对象的同步方法和非同步方法,不互斥; */ public class Test02 { public static void main(String[] args) throws InterruptedException { Test02 t = new Test02(); new Thread(() -> t.eat(), "t1").start(); new Thread(() -> t.work(), "t2").start(); } //同步方法 public synchronized void eat() { while (true) { System.out.println(Thread.currentThread().getName() + " 吃饭......" + System.currentTimeMillis()); ThreadUtils.sleepMilliSeconds(300); } } //非同步方法 public void work() { while (true) { System.out.println(Thread.currentThread().getName() + " 工作......" + System.currentTimeMillis()); ThreadUtils.sleepMilliSeconds(300); } } } ##### 3、两个线程同时访问两个对象的同步方法,不互斥; ##### /** * 两个线程同时访问两个对象的同步方法,不互斥; * */ public class Test03 { public static void main(String[] args) throws InterruptedException { Test03 t1 = new Test03(); Test03 t2 = new Test03(); new Thread(() -> t1.eat(), "t1").start(); new Thread(() -> t2.eat(), "t2").start(); } //同步方法 public synchronized void eat() { while (true) { System.out.println(Thread.currentThread().getName() + " 吃饭......" + System.currentTimeMillis()); ThreadUtils.sleepMilliSeconds(300); } } } ##### 4、两个线程同时访问两个对象的同步方法和非同步方法,不互斥; ##### /** * 两个线程同时访问两个对象的同步方法和非同步方法,不互斥; */ public class Test04 { public static void main(String[] args) throws InterruptedException { Test04 t1 = new Test04(); Test04 t2 = new Test04(); new Thread(() -> t1.eat(), "t1").start(); new Thread(() -> t1.work(), "t1").start(); new Thread(() -> t2.eat(), "t2").start(); new Thread(() -> t2.work(), "t2").start(); } public synchronized void eat() { while (true) { System.out.println(Thread.currentThread().getName() + " 吃饭......" + System.currentTimeMillis()); ThreadUtils.sleepMilliSeconds(300); } } public void work() { while (true) { System.out.println(Thread.currentThread().getName() + " 工作......" + System.currentTimeMillis()); ThreadUtils.sleepMilliSeconds(300); } } } #### 2.4.4类锁 #### ##### 1、两个线程同时访问同一个类的静态同步方法,会互斥; ##### /** * 两个线程同时访问同一个类的静态同步方法,会互斥; * */ public class Test05 { //Test05.class --> Class public static void main(String[] args) throws InterruptedException { new Thread(() -> eat(), "t1").start(); new Thread(() -> eat(), "t2").start(); } public synchronized static void eat() { while (true) { System.out.println(Thread.currentThread().getName() + " 吃饭......" + System.currentTimeMillis()); ThreadUtils.sleepMilliSeconds(300); } } } ##### 2、两个线程同时访问不同类的静态同步方法,不互斥; ##### /** * 两个线程同时访问不同类的静态同步方法,不互斥; * */ public class Test06 { public static void main(String[] args) throws InterruptedException { new Thread(() -> eat(), "t1").start(); new Thread(() -> Cat.eat(), "t2").start(); } public synchronized static void eat() { while (true) { System.out.println(Thread.currentThread().getName() + " 吃饭......" + System.currentTimeMillis()); ThreadUtils.sleepMilliSeconds(300); } } } ##### 3、两个线程同时访问同一个类的静态同步方法和静态非同步方法,不互斥; ##### /** * 两个线程同时访问同一个类的静态同步方法和静态非同步方法,不互斥; * */ public class Test07 { public static void main(String[] args) throws InterruptedException { new Thread(() -> eat(), "t1").start(); new Thread(() -> work(), "t2").start(); } public synchronized static void eat() { while (true) { System.out.println(Thread.currentThread().getName() + " 吃饭......" + System.currentTimeMillis()); ThreadUtils.sleepMilliSeconds(300); } } public static void work() { while (true) { System.out.println(Thread.currentThread().getName() + " 工作......" + System.currentTimeMillis()); ThreadUtils.sleepMilliSeconds(300); } } } ##### 4、两个线程同时访问同一个类的静态同步方法和非静态同步方法,不互斥; ##### /** * 两个线程同时访问同一个类的静态同步方法和非静态同步方法,不互斥; * */ public class Test08 { public static void main(String[] args) throws InterruptedException { Test08 t = new Test08(); new Thread(() -> eat(), "t1").start(); new Thread(() -> t.work(), "t2").start(); } public synchronized static void eat() { while (true) { System.out.println(Thread.currentThread().getName() + " 吃饭......" + System.currentTimeMillis()); ThreadUtils.sleepMilliSeconds(300); } } public synchronized void work() { while (true) { System.out.println(Thread.currentThread().getName() + " 工作......" + System.currentTimeMillis()); ThreadUtils.sleepMilliSeconds(300); } } } **- 总结:使用的是同一把锁就互斥,不是同一把锁,要不会互斥!** ### 2.5synchronized底层实现原理剖析 ### **synchronized锁的类型分为:偏向锁、轻量级锁、重量级锁;** JDK1.6之前,synchronized只有重量级锁,JDK1.6之后新增了偏向锁、轻量级锁,这是JDK的一种锁优化提升; #### 2.5.1JVM堆内存中的对象布局? #### 在 HotSpot 虚拟机中,一个对象的存储结构分为3块区域: * **(1)对象头(Header)** * **(2)实例数据(Instance Data)** * **(3)对齐填充(Padding);** ![在这里插入图片描述][2b1b3e1ee9fa474b90c5e35eec87d49d.png] ##### 对象头(Header): ##### 第一部分是Mark Word,用于存储对象自身的运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等,32位虚拟机占32 bit,64位虚拟机占 64 bit,下图分别是32位和64位的Mark Word存储结构: ![在这里插入图片描述][660228d2b4884a0193ea32616cb67d79.png] * 通过2bit表示锁状态,通过1bit表示无锁和偏向锁; * 第二部分是类型指针,即对象指向它的类的元数据指针,虚拟机通过这个指针确定这个对象是哪个类的实例; * (第三部分) 如果是Java数组,对象头中还必须有一块用于记录数组长度的数据,因为普通对象可以通过 Java 对象元数据确定大小,而数组对象不可以; ##### 实例数据(Instance Data): ##### 程序代码中所定义的各种成员变量类型的字段内容(包含父类继承下来的和子类中定义的); ##### 对齐填充(Padding): ##### 不是必然需要,主要是占位,保证对象大小是某个字节的整数倍,HotSpot虚拟机,任何对象的大小都是8字节的整数倍,可以通过-XX:ObjectAlignmentInBytes=16调整默认的对齐大小为16 bytes的倍数; #### 2.5.2JOL查看Java对象内存布局 #### JOL (Java?Object Layout),通过JOL输出对象内存布局: <!-- 查看Java对象内存布局 --> <dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.14</version> </dependency> ![在这里插入图片描述][9c3ed8fbe9c048a8a90fe5dbde0024b4.png] * **OFFSET**:偏移地址,单位:字节; * **SIZE**:占用内存大小,单位:字节; * **TYPE DESCRIPTION**:类型描述,其中(object header)表示对象头,单位:字节,一共占用12字节,前面8字节对应对象头中的Mark Word,最后4字节对应类型指针(类型指针默认进行了压缩是4字节,通过-XX:-- - - UseCompressedOops关闭指针压缩,若没有压缩就会变成8字节); (loss due to the next object alignment) 表示对齐填充,对齐填充使用了4字节,从而保证整个大小是8字节的整数倍; * **Instance size**: 32 bytes表示当前对象占32字节; * **Space losses**: 0 bytes internal + 4 bytes external = 4 bytes total 内部及外部的空间损失; * **VALUE**:对应内存中当前存储的值; **【衔接下一章 [【并发编程九:线程安全问题分析及锁的介绍(2)】][2]】** [Java_4_-]: https://blog.csdn.net/weixin_43333483/article/details/124557296?spm=1001.2014.3001.5502 [3fad68bc2e284b94b5920e9ae67e6400.png]: https://img-blog.csdnimg.cn/3fad68bc2e284b94b5920e9ae67e6400.png [3126c5bc5c554e16ac8b9d9fa69275c7.png]: https://img-blog.csdnimg.cn/3126c5bc5c554e16ac8b9d9fa69275c7.png [819c19b956194cd99f060d954ab0ebe5.png]: https://img-blog.csdnimg.cn/819c19b956194cd99f060d954ab0ebe5.png [1457cc896a7748a6a8cb275b617861da.png]: https://img-blog.csdnimg.cn/1457cc896a7748a6a8cb275b617861da.png [2b1b3e1ee9fa474b90c5e35eec87d49d.png]: https://img-blog.csdnimg.cn/2b1b3e1ee9fa474b90c5e35eec87d49d.png [660228d2b4884a0193ea32616cb67d79.png]: https://img-blog.csdnimg.cn/660228d2b4884a0193ea32616cb67d79.png [9c3ed8fbe9c048a8a90fe5dbde0024b4.png]: https://img-blog.csdnimg.cn/9c3ed8fbe9c048a8a90fe5dbde0024b4.png [2]: https://blog.csdn.net/weixin_43333483/article/details/124558495?spm=1001.2014.3001.5501
相关 Java并发编程:线程安全问题分析及解决方案 在Java中,线程安全是一个重要的话题。它通常涉及到多个线程同时访问和修改共享数据的问题。 线程不安全的表现: 1. 无锁竞态(Race Condition):两个或更多的 Dear 丶/ 2024年09月16日 18:58/ 0 赞/ 19 阅读
相关 Java并发编程:线程安全问题实例分析 在Java的多线程环境中,线程安全是一个关键的概念。线程安全是指一个对象在多线程环境下正确执行不会产生意外结果的状态。 以下是一些具体的线程安全问题实例: 1. **共享变 电玩女神/ 2024年09月16日 06:03/ 0 赞/ 24 阅读
相关 并发编程:Java线程安全问题分析 在Java的并发编程中,线程安全是一个重要且复杂的问题。以下是针对这一问题的一些分析: 1. **锁(Lock)**:最基础的线程同步工具,通过`java.util.conc 女爷i/ 2024年09月15日 05:15/ 0 赞/ 27 阅读
相关 Java并发编程:线程安全问题案例分析 在Java并发编程中,线程安全问题主要表现为多个线程同时访问共享资源,导致数据不一致或错误。以下是一些常见的问题案例: 1. **单例模式的线程安全问题**: - 传统 落日映苍穹つ/ 2024年09月05日 13:39/ 0 赞/ 22 阅读
相关 【并发编程九:线程安全问题分析及锁的介绍(2)synchronized】 【衔接上一章 [【并发编程八:线程安全问题分析及锁的介绍(1)】][1]】 2.5.3对象头Mark Word中VALUE值解读 Intel、AMD处理器(CPU)是小 Myth丶恋晨/ 2023年10月09日 14:09/ 0 赞/ 7 阅读
相关 【并发编程八:线程安全问题分析及锁synchronized的介绍(1)】 【衔接上一章 [【并发编程七:Java中的线程池(4)-线程池规范、监控、关闭】][Java_4_-]】 学习路线 2.1什么是线程安全? 2 灰太狼/ 2023年10月09日 14:09/ 0 赞/ 10 阅读
相关 Java并发编程(二) : 线程安全问题、synchronized保证线程安全、private或final的重要性、线程八锁问题分析、变量的线程安全分析 > 承接上文: ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9n 谁借莪1个温暖的怀抱¢/ 2022年12月29日 01:57/ 0 赞/ 86 阅读
相关 Java多线程编程-(1)-线程安全和锁Synchronized概念 \\一、进程与线程的概念 \\ (1)在传统的操作系统中,程序并不能独立运行,作为资源分配和独立运行的基本单位都是进程。 在未配置 OS 的系统中,程序的执行方式是顺序执行 谁践踏了优雅/ 2022年06月08日 00:59/ 0 赞/ 148 阅读
相关 Java多线程编程-(1)-线程安全和锁Synchronized概念 > 原文出自 : [https://blog.csdn.net/xlgen157387/article/details/77920497][https_blog.csdn.ne 缺乏、安全感/ 2021年09月28日 01:26/ 0 赞/ 315 阅读
还没有评论,来说两句吧...