一、设计同步器的意义

多线程编程中,有可能会出现多个线程同时访问同一个共享、可变资源的情况,这个资源我们称之其为临界资源;这种资源可能是:对象、变量、文件等。
共享:资源可以由多个线程同时访问
可变:资源可以在其生命周期内被修改
引出的问题:由于线程执行的过程是不可控的,所以需要采用同步机制来协同对对象可变状态的访问那么我们怎么解决线程并发安全问题?实际上,所有的并发模式在解决线程安全问题时,采用的方案都是 序列化访问临界资源。即在同一时刻,只能有一个线程访问临界资源,也称作同步互斥访问。Java 中,提供了两种方式来实现同步互斥访问:synchronized 和 Lock同步器的本质就是加锁加锁目的:序列化访问临界资源,即同一时刻只能有一个线程访问临界资源(同步互斥访问)不过有一点需要区别的是:当多个线程执行一个方法时,该方法内部的局部变量并不是临界资源,因为这些局部变量是在每个线程的私有栈中,因此不具有共享性,不会导致线程安全问题。
注意,锁保证了线程执行的有序性,但是一个线程拿到锁执行代码,并不能保证这段代码的有序性执行,即可能会发生指令重排;

二、synchronized锁原理解析

synchronized内置锁是一种对象锁(锁的是对象而非引用),作用粒度是对象,可以用来实现对临界资源的同步互斥访问,是可重入的。加锁的方式:
1、同步实例方法,锁是当前实例对象
2、同步类方法,锁是当前类对象
3、同步代码块,锁是括号里面的对象
 

三、synchronized底层原理

synchronized是基于JVM内置锁实现,通过内部对象Monitor(监视器锁)实现,java中每个对象都有一个内置对象Monitor(监视器锁)。基于进入与退出Monitor对象实现方法与代码块同步,监视器锁的实现依赖底层操作系统的Mutex lock(互斥锁)实现,它是一个重量级锁性能较低。当然,JVM内置锁在1.5之后版本做了重大的优化,如锁粗化(LockCoarsening)、锁消除(Lock Elimination)、轻量级锁(LightweightLocking)、偏向锁(Biased Locking)、适应性自旋(Adaptive Spinning)等技术来减少锁操作的开销,,内置锁的并发性能已经基本与Lock持平。
synchronized关键字被编译成字节码后会被翻译成monitorenter 和monitorexit 两条指令分别在同步块逻辑代码的起始位置与结束位置。

既然每个对象都有一个内置对象Monitor,我们如何看到这内置对象Monitor,以及这个对象锁都有哪些内容呢,这个我们就需要查看jvm源码,
查看jdk源码  \openjdk\hotspot\src\share\vm\runtime   在这个目录下找到objectMonitor.hpp(c++编写)查看jdk原代码
ObjectMonitor() {
_header = NULL; //对象头
_count = +++---; //记录加锁次数,锁重入时用到
_waiters = , //当前有多少处于wait状态的thread
_recursions = ;
_object = NULL;
_owner = ; //指向持有ObjectMonitor对象的线程
_WaitSet = NULL; //处于wait状态的线程,会被加入到_WaitSet
_WaitSetLock = ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ;//处于等待加锁block状态的线程,会被加入到该列表
_SpinFreq = ;
_SpinClock = ;
OwnerIsThread = ;
_previous_owner_tid = ;
}

我们这个每个对象都有内置对象Monitor,那这个内置对象存在我们对象的哪里呢,这就需要了解在java里面我们的对象的内存划分,看下图:

认识对象的内存结构:

1、对象头:比如 hash码,对象所属的年代,对象锁,锁状态标志,偏向锁(线程)ID,偏向时间,数组长度(数组对象)等

2、对象实际数据:即创建对象时,对象中成员变量,方法等

3、对齐填充:对象的大小必须是8字节的整数倍

可见,和锁相关的信息比如锁的次数,锁的状态都在都对象头里面存储,另外MetaData(元数据指针)存的是指向类对象信息,所以每个对象getClass可以获取类对象。

jvm对象整体加锁过程:

四、逃逸分析

面试问题:实例对象内存中存储在哪

答:如果实例对象存储在堆区时:实例对象内存存在堆区,实例的引用存在栈上,实例的元数据class存在方法区或者元空间

Object实例对象一定是存在堆区的吗

不一定,如果实例对象没有线程逃逸行为

使用逃逸分析,编译器可以对代码做如下优化:
一、同步省略。如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以不考虑同步。
二、将堆分配转化为栈分配。如果一个对象在子程序中被分配,要使指向该对象的指针永远不会逃逸,对象可能是栈分配的候选,而不是堆分配。
三、分离对象或标量替换。有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而是存储在CPU寄存器中。
是不是所有的对象和数组都会在堆内存分配空间?
不一定
在Java代码运行时,通过JVM参数可指定是否开启逃逸分析,-XX:+DoEscapeAnalysis : 表示开启逃逸分析 -­XX:­-DoEscapeAnalysis : 表示关闭逃逸分析 从jdk 1.7开始已经默认开始逃逸分析,如需关闭,需要指定-­XX:-­DoEscapeAnalysis。
总结一句话:java1.7默认开启了逃逸分析,在开启逃逸分析的情况下,如果一个对象只能够在某一个线程被访问到而不会被其他线程访问,那么这个对象是有可能存在栈内存的,所以不是所有的的对象都在堆内存。
 

并发编程之synchronized锁(一)的更多相关文章

  1. Java并发编程之synchronized关键字

    整理一下synchronized关键字相关的知识点. 在多线程并发编程中synchronized扮演着相当重要的角色,synchronized关键字是用来控制线程同步的,可以保证在同一个时刻,只有一个 ...

  2. Java 多线程并发编程之 Synchronized 关键字

    synchronized 关键字解析 同步锁依赖于对象,每个对象都有一个同步锁. 现有一成员变量 Test,当线程 A 调用 Test 的 synchronized 方法,线程 A 获得 Test 的 ...

  3. 并发编程之synchronized(二)------jvm对synchronized的优化

    一.锁的粗化 看如下代码 public class Test { StringBuffer stb = new StringBuffer(); public void test1(){ //jvm的优 ...

  4. 高并发编程之synchronized

    一.什么是线程? 线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元.一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成.另外,线程 ...

  5. 并发编程之Java锁

    一.重入锁 锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized(重量级) 和 ReentrantLock(轻量级)等等 ) .这些已经写好提供的锁为我们开发提 ...

  6. Java并发编程之synchronized

    在Java编程中,为了保证线程安全,有3种不同的思路1.互斥同步:包括synchronized和lock等. 2.非阻塞同步:如AtomicInteger的increaseAndGet()方法等. 3 ...

  7. 并发编程之synchronized关键字

    synchronized关键字 synchronized关键字最主要的三种使用方式的总结 1.修饰实例方法,作用于当前对象实例加锁,进入同步代码块前要获得当前对象实例的锁 2.修饰静态方法,作用于当前 ...

  8. 并发编程之wait()、notify()

    前面的并发编程之volatile中我们用程序模拟了一个场景:在main方法中开启两个线程,其中一个线程t1往list里循环添加元素,另一个线程t2监听list中的size,当size等于5时,t2线程 ...

  9. 并发编程之J.U.C的第二篇

    并发编程之J.U.C的第二篇 3.2 StampedLock 4. Semaphore Semaphore原理 5. CountdownLatch 6. CyclicBarrier 7.线程安全集合类 ...

随机推荐

  1. Hunter’s Apprentice(判断所走路线为顺时针或逆时针)【Green公式】

    Hunter's Apprentice 题目链接(点击) 题目描述 When you were five years old, you watched in horror as a spiked de ...

  2. iOSdelegate、Notification、block区别

    1.delegate与block一般用于1对1的通信交互.delegate需先定义好协议方法,代理对象实现协议方法且需要建立代理关系才可以通信. 2.block使用简洁,如果通信事件较多,建议使用de ...

  3. Python绘图之Turtle库详解(1)

    Turtle库是Python语言中一个很流行的绘制图像的函数库,想象一个小乌龟,在一个横轴为x.纵轴为y的坐标系原点,(0,0)位置开始,它根据一组函数指令的控制,在这个平面坐标系中移动,从而在它爬行 ...

  4. c常用函数-atoi 和 itoa

    atoi 和 itoa atoi的功能是把一个字符串转为整数 Action(){ int j; char *s=""; j = atoi(s); lr_output_message ...

  5. IOS App破解之路一 拿到appstore上的ipa

    1,  在Mac电脑上的app store里搜索Apple Configurator2 并安装 2, iPhone手机连接Mac电脑 3, 登录Apple Configurator2 菜单栏,  账号 ...

  6. MySQL LOAD DATA INFILE—批量从文件(csv、txt)导入数据

    最近做的项目,有个需求(从Elastic Search取数据,业务运算后),每次要向MySQL插入1300万数据左右.最初用MySQL的executemany()一次插入10000条数据,统计的时间如 ...

  7. sed 命令使用入门

    上一篇说了 awk 命令的基本使用方法,这一篇就来说说其兄弟 sed 的使用方法吧(传说之中,Linux 命令行下处理文件文件三大上古神器:grep.awk.sed,每一个都很好很强大,有时间了说说 ...

  8. 【String注解驱动开发】你了解@PostConstruct注解和@PreDestroy注解吗?

    写在前面 在之前的文章中,我们介绍了如何使用@Bean注解指定初始化和销毁的方法,小伙伴们可以参见<[Spring注解驱动开发]如何使用@Bean注解指定初始化和销毁的方法?看这一篇就够了!!& ...

  9. 【原】二进制部署 k8s 1.18.3

    二进制部署 k8s 1.18.3 1.相关前置信息 1.1 版本信息 kube_version: v1.18.3 etcd_version: v3.4.9 flannel: v0.12.0 cored ...

  10. JavaScript基础-自己定义自己的函数(016)

    把一个函数对象赋值给变量后,就可以通过这个变量再次定义函数,甚至可以在一个函数内部再次定义它自己: var scareMe = function () { alert("Boo!" ...