一、关于临界区、临界资源、竞态条件和解决方法

首先看如下代码,thread1对变量i++做500次运算,thread2对i--做500次运算,但是最终的结果却可能为是正数,负数,0不一样的结果。

package com.spj.synch;import lombok.extern.slf4j.Slf4j;@Slf4j
public class Synch1 {//临界资源private static int i=0;//临界区public static void increase(){i++;}//临界区public static void decrease(){i--;}public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread(() -> {for (int j = 0; j < 500; j++) {increase();}}, "Thread1");Thread thread2 = new Thread(() -> {for (int j = 0; j < 500; j++) {decrease();}}, "Thread2");thread1.start();;thread2.start();thread1.join();thread2.join();log.info("{}", i);}
}

在单线程情况下,上面的代码最终结果为0,但是多线程共享资源时就会出现问题。

临界区、临界资源、竞态条件:

在一个代码块中,如果存在多线程对共享资源的读写操作,那么这块代码就称为临界区,该资源称为临界资源。上面代码中i就是临界资源,对i加减操作的代码块就是临界区。threa1,thread2在临界区内执行,由于代码的执行序列不同而导致结果无法预测,这样就发生了所谓的竞态条件。

而解决这种线程安全的问题,就有两种解决方法

一是阻塞式的,二是非阻塞式的,而Sychronized就是阻塞式的解决方法。

        synchronized的使用 :

sychronized加锁方式有两种:一种是加在方法上:

public static synchronized void increase(){i++;}public static synchronized void decrease(){i--;}

另外一种是加在代码块中,被锁住的是对象:

private static String lock="";public static  void increase(){synchronized(lock){i++;}}public static  void decrease(){synchronized(lock){i--;}}

将原来代码加上锁后就不会出现上面的线程安全问题,运行结果为0:

        另外一点就是synchronized对于这两种加锁方法各有区别,同步方法是通过方法中的access_flags中设置ACC_SYNCHRONIZED标志来实现;同步代码块是通过monitorenter和monitorexit来实现。

二、synchronized的实现

        synchronized是JVM内置锁,它是基于monitor管程来实现的。Java中synchronized参考了管程MESA模型:

就是当有一个线程获取资源时,会加锁使其他线程不能获取资源,从而陷入阻塞等待状态,这些线程就放入阻塞等待队列里面,直到获取资源的线程释放资源,调用wait()/notify()方法才将其他线程唤醒,然后获取资源。

由于synchronized是基于monitor机制实现的,而monitor又是基于Object对象实现的,在Object类中定义了 wait(),notify(),notifyAll() 方法,这些方法的实现基于ObjectMonitor类实现,ObjectMonitor主要数据结构有_cxp(单向链表组成的栈结构),_waitSet队列(等待线程组成的双向循环链表结构),_EntryList队列(存放竞争锁失败的线程),_header 对象头等。该机制大致流程可如下:

线程竞争锁时,是将当前线程插入_cxq头部,释放锁时,如果_EntryList为空,则将_cxq栈中元素安装原有顺序插入_EntryList队列中,然后唤醒第一个线程(默认情况下),由此可知synchronized为不公平锁;而若_EntryList不为空则其内部线程直接获取锁。

synchronized中的锁状态有如下四种状态:

1.无锁(001):没有加锁的状态

2. 偏向锁(101):引入偏向锁的原因是有的情况下锁不存在多线程之间的竞争,引入偏向锁可以消除数据在无竞争的情况下进行的CAS锁重入的开销。JVM启动后会有一个默认的偏向锁延迟机制操作,就是启动4s后默认开启偏向锁模式。

3.轻量级锁(00):在某些情况下,存在轻微的竞争,但是竞争并不强烈的情况下,偏向锁不会立即升级为重量级锁,而是轻量级锁,该锁适用于存在线程交替执行但是并不激烈的场合。

4.重量级锁(10):当高并发线程间竞争激烈的场景下,偏向锁,轻量级锁或者是无所状态都会变为重量级锁,此时会生成一个monitor对象,从而转换到内核态。

那么synchronized中是如何记录锁状态的呢,这就涉及到对象在内存中的布局如下图

也就是说,对象在内存中可分为三部分:

对象头:存放对象的hashCode,分代年龄,锁状态标志,偏向锁ID等

实例数据:存放对象的属性信息

对齐填充位置:保证对象的起始地址是8的整数倍,不是的时候会自动填充,确保是8的整数倍。

对于对象在内存中占的内存大小:

1.Mark word占8字节

2.KlassPointer指针开启指针压缩后占4字节(默认),关闭的话则占8字节

3.如果是数组对象则数组长度占8字节

4.除了上面三点外,还要加上对象属性占的内存。

5.最终加起来的结果如果不是8的整数倍,则要对齐填充,确保使结果是8的整数倍。

比如下面的对象:

package com.spj.synch;import org.openjdk.jol.info.ClassLayout;public class spj {public static void main(String[] args) {System.out.println(ClassLayout.parseInstance(new A()).toPrintable());}public static class A {private boolean flag;}
}

该对象不是数组对象,故算上上面讲的1、2两点就占了12个字节,Boolean类型占1个字节一共是13个字节,此时不是8的整数倍,则对齐填充后在内存中占16个字节。

我们可以引入如下依赖来查看对象在内存中的布局

<dependency><groupId>org.openjdk.jol</groupId><artifactId>jol‐core</artifactId><version>1.0</version></dependency>

运行的结果如下

讲完了对象布局,接下来是锁的状态如何存放的。

synchronized中,锁的状态存放在Mark Word中,其中用101表示偏向锁,001表示无锁,00表示轻量级锁,10表示重量级锁。

那么,对于这四种锁的状态,他们之间的转换关系是怎样的呢?可如下图所示

最后,对于偏向锁撤销而言,会花费很多的开销,在高并发竞争激烈的情况下,开销大,因此JVM中对synchronized锁进行了优化 ,即偏向锁批量重偏向、批量撤销以及自旋优化,即使用epoch计数,每次偏向锁撤销时该计数器都会+1

1.偏向锁批量重偏向:当有大量的线程竞争锁,假设这些锁分为左边和右边部分,开始的时候偏向锁偏向的是左边的线程,当计数器值达到20(默认值)时,JVM会认为偏向左边线程不是出了问题,会重新进行偏向右边的线程。

2.批量撤销:当epoch值达到40(默认值)时,JVM会直接将后面的偏向锁置为无锁状态,这样就减少了偏向锁撤销的开销。

  3.自旋优化:在重量级锁竞争中,如果某线程竞争锁失败开始膨胀,则可以让它再次尝试去获取锁,再失败再尝试,这就是自旋的优化。假设这个过程中直接获得了锁,此时就可以直接避免阻塞。

Java并发编程synchronized详解相关推荐

  1. Java 并发编程_详解 synchronized 和 volatile

    文章目录 1. synchronized 的应用 1.1 基础知识 1.2 synchronized 语法 2. Monitor概念 3. Synchronized原理进阶 3.1 对象头格式 3.2 ...

  2. Java并发编程AQS详解

    本文内容及图片代码参考视频:https://www.bilibili.com/video/BV12K411G7Fg/?spm_id_from=333.788.recommend_more_video. ...

  3. Java并发编程——ConcurrentHashMap详解

    引出 场景:针对用户来做一个访问次数的记录. 通过HashMap进行记录,key为用户名,value为访问次数. public class ConcurrentHashMapDemo {private ...

  4. Java并发编程——ForkJoin详解

    概念 Fork/Join 框架是 Java7 提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架.类似于Java 8中的paralle ...

  5. 它来了,阿里架构师的“Java多线程+并发编程”知识点详解手册,限时分享

    自学Java的时候,多线程和并发这一块可以说是最难掌握的部分了,很多小伙伴表示需要一些易于学习和上手的资料. 所以今天这份「Java并发学习手册」就是一份集中学习多线程和并发的手册,PDF版,由Red ...

  6. Java并发编程—Synchronized底层优化(偏向锁、轻量级锁)

    原文作者:Matrix海 子 原文地址:Java并发编程:Synchronized底层优化(偏向锁.轻量级锁) 目录 一.重量级锁 二.轻量级锁 三.偏向锁 四.其他优化 五.总结 一.重量级锁 上篇 ...

  7. Java 并发编程—Synchronized关键字

    原文作者:liuxiaopeng 原文地址:Java并发编程:Synchronized及其实现原理 目录 一.Synchronized的基本使用 二.Synchronized 原理 三.运行结果解释 ...

  8. Java并发编程 synchronized保证线程安全的原理

    文章转载致博客 blog.csdn.net/javazejian/- 自己稍加完善. 线程安全是并发编程中的重要关注点,应该注意到的是,造成线程安全问题的主要诱因有两点,一是存在共享数据(也称临界资源 ...

  9. 并发编程 — AtomicStampedReference 详解

    AtomicInteger.AtomicBoolean.AtomicLong.AtomicReference 这些原子类型,它们无一例外都采用了基于 volatile 关键字 +CAS 算法无锁的操作 ...

最新文章

  1. RGB Color Codes Chart
  2. 图解ZooKeeper!小学生也能看懂!
  3. 字节流和字符流复制文件内容实例
  4. Rainmeter 天气
  5. 12产品经理要懂的-人性满足思维
  6. windows系统清理磁盘临时文件,及缓冲文件,及离线文件和空闲文件
  7. 学MySQL,这篇万字总结,真的够用了
  8. 云原生分布式数据库和数据仓库崛起背后的原因
  9. dota2连接服务器没有响应,win10系统dota2无法与任何服务器建立连接的解决方法
  10. python 绘图库_Python安装可视化绘图库,你真的会了吗?一文告诉你全部
  11. 【计算机网络】计网笔记知识点整理篇(1-3章,后续章节持续更新)
  12. Annoying Present CodeForces - 1009C
  13. 15 个有趣的 JS 和 CSS 库
  14. SQL Server报错:Arithmetic overflow error converting expression to data type int.
  15. 录制失败因为媒体服务失败_啊啊啊啊!原来戚风失败是因为……
  16. 支付宝小程序map地图
  17. 红木装修——重现新时代下的东方神韵
  18. Android S WLAN 架构
  19. 《代码精进之路》第一章:命名 读书笔记
  20. 跑跑卡丁车单机版车辆编号

热门文章

  1. 碰撞、子弹路径、参考
  2. Python爬虫:(亲测,已解决!)解决在使用谷歌浏览器的开发者工具时,没有Referer防盗链缺失问题。
  3. 职场小白如何将图片转文字?这个方法建议收藏使用!
  4. 使用Nexus添加jar包到私服里
  5. 使用Visual Studio怎样制作登录界面
  6. 咪咕版kindle利用“隐藏浏览器”打开微信读书
  7. jQuery——滚动条位置的获取与设置
  8. ffiddler抓取手机(app)https包
  9. WIN32 opengl缩放、旋转、移动图形
  10. Android从零开始搭建MVVM架构(3)——ViewModel