一直想把这个特别重要的关键词的底层实现搞明白。(当然现在也没有完全明白,如果有错误以后修改这篇文章)

首先,这个关键词synchronize可以说是个语法糖,它的具体用法网上很多博客都讲的比较明了了。

简而言之就是对一个对象“加锁”。首先,找个地方的对象不一定是堆里面的类的实例对象,也有可能是方法区的类对象。其次,这个关键词修饰的代码块的加锁过程有两个,进入的时候尝试获得锁(java字节码 monitorenter),退出时释放锁(java字节码monitorexit)。这两个操作的再下一层是基于mutex lock的lock()和unluck()。

这两个函数的具体实现由操作系统提供。lock()操作是“获得锁”“上锁”“进入临界区”,等等,不同的地方描述不一致。它的具体过程是:查看一个信号量(由这个锁持有),看当前能否获得锁,如果能直接获得,并且修改这个信号量的值(比如把1改成0)。如果不能,就把这个索取锁的线程自己加入一个队列,这队列专门放“困”在这个对象(这个锁)上的线程,接着阻塞这个线程自己。unlock()操作是“释放锁”“解锁”“离开临界区”。他可以直接修改信号量的值。同时他看是否有进程“困”在这个对象(锁)上。如果有,唤醒它放入就绪队列。(信号量的具体实现各不相同,记录型信号量可以更方便理解这个过程)。当然这些基础操作也是原子性的。

这个地方还有一个很重要的点,锁,线程,对象这3个东西到底怎么联系到一起的。而要讲明白这个,又不得不讲一下锁不一定是重量级的由操作系统提供的“互斥锁”。还有一种锁:轻量级锁。这种锁是一种运行时优化,如果用synchronize修饰的代码块没有发生并发行为就可以直接用这种“锁”。

一开始,要明白Mark Word。这是每一个对象的对象头中有的一个32bit或者64bit(由JVM确定)的一个区域(叫Mark Word,对象头还有一个和它一样大的区域保存了一个指针指向方法区的类对象)。保存了对象在运行过程中的一些数据,比如哈希码,GC分代年龄,上锁标志位等等。它的存在是必要的,因为确实有些运行时信息要通过这种形式保留。一个线程根据java字节码找到上锁的对象,查看上锁标志位,看看是否已经被别的线程获得锁,如果没有,就在这个线程的栈帧中建立一个锁记录(Lock Record,有的地方叫Monitor Record),保存这个Mark Word的一份拷贝,接着用一个Owner指针指向这个对象。对象则直接把Mark Word改成一个指针指向这个锁记录。(记得前面提到Mark Word的大小是刚好和当前操作系统指针的大小一样,所以可以直接改而不需要补位等操作)

但是这个地方一开始我很不理解,为什么要这么绕呢?既然锁在对象上面,为什么不直接在对象头保留一个空间,记录或者这个“锁”的线程呢,比如用线程的ID或者内部标识符。每次线程进入临界区,访问这个对象的时候直接去对象头看这个值是不是自己这个线程,如果不是就阻塞自己。

原因是:对象头是很珍贵的,因为每一个对象都有,它虽然有必要但是它的内容又确实不是对象本身真正的内容。也就是说要想尽一切办法缩小它的大小否则效率很低(试想你开辟的空间有一大半都储存了一些杂七杂八的信息)。相比这个对象头,运行时的程序栈可谓是非常广阔的空间。一个珍惜这点空间一个无所谓这点空间。这样就刚好通过“复用”来实现储存空间的优化。把本来需要额外增加的空间直接用Mark Word储存而它本来的值丢给线程的栈。这样就一举两得,首先储存了持有这个对象的锁的线程,同时也没有弄丢Mark Word(反正我用了指针指向这个线程也不怕找不到)。而线程则再用一个指针Owner指向这个对象。很完美。

这个是轻量级锁的做法,如果不是轻量级呢?其实JVM的优化策略保证了一开始都把他当做轻量级来处理(JVM的优化策略有自旋锁,锁消除,锁粗化,轻量级锁,偏向锁等等)。这个地方也要解释2点,第一,好处都有啥,第二,为什么能这么做。第三,为什么要这么做。

首先,如果直接用操作系统提供的Mutex Lock互斥锁的话,会使用操作系统调用,从用户态转为核心态,开销很大。用这种方式(轻量级锁)则只是一个CAS操作(要保证其原子性)的花费。第二,马上下面讲到如果轻量级锁没用了(也就是发生了竞争别的线程试图拿这个锁),它可以直接“膨胀”成一个重量级锁(Mutex Lock)。第三,现实情况是很多加了synchronize修饰的代码其实在实际运行过程中并没有发生竞争的情况,这么做在运行时直接减少了很多开销。

然后,什么情况下会从一个轻量级膨胀成一个重量级的Mutex Lock呢?其实jvm这部分的优化是这样的,一开始先“认为”这次加锁和大多数情况一样并没有发生竞争,于是先“机智”地用轻量级,这个时候如果发生竞争,也就是有别的线程尝试获得锁,就“膨胀”为一个重量级。再具体一点就是别的线程调用Lock(),发现当前这个对象的对象头的标志位是“加了轻量级锁的”。它再去看Owner,如果是自己就是一个“重入”。如果不是就说明发生了竞争,接着进行“膨胀”。这是第一种可能,也就是一个线程先在跑,后一个加入发生膨胀。其实还有第二种可能,发生在前一个线程刚放锁的时候,这个时候所有线程都认为没锁,同时通过CAS竞争,有一个会成功,其他的会失败,于是也进行膨胀。

接着来讲膨胀的过程。第一,改变对象锁标志的状态值,把Mark Word中保存的指针指向Mutex Lock(当然Mark Word的内容还是不能丢)我看的是深入理解Java虚拟机这本书,再加上一些网络上的博客。都没把这个部分讲明白,这个地方我“猜”一下:调用操作系统互斥锁,生成一个互斥锁并且把Mark Word的值指向它。同时让这个互斥锁或者别的什么数据结构和方式保存Mark Word原本的运行时信息。这个地方的Mutex Lock可能就是真真正正的“重量级”锁了,它的具体实现我估计和记录型信号量的PV原语操作差不多,同时还要保留一些标志位储存Owner,重入个数,等待队列的元素个数,等待队列指针,如果当前释放锁是否有线程需要唤醒等信息。

以上内容能够保证正确的是Mark Word指向一个Mutex Lock。这之后的过程就是再有线程调用Lock()尝试访问临界区,发先对象头指向一个锁,再进入锁发现已经上了锁并且自己不是Owner,于是就阻塞自己。出临界区的时候去唤醒别的阻塞线程。这都没啥说的了。

java多线程synchronized底层实现的更多相关文章

  1. java多线程02-----------------synchronized底层实现及JVM对synchronized的优化

    java多线程02-----------------synchronized底层实现及JVM对synchronized的优化 提到java多线程,我们首先想到的就是synchronized关键字,它在 ...

  2. Java 多线程 —— synchronized关键字

    java 多线程 目录: Java 多线程——基础知识 Java 多线程 —— synchronized关键字 java 多线程——一个定时调度的例子 java 多线程——quartz 定时调度的例子 ...

  3. java 多线程 synchronized与lock的通信机制等问题,结合相应实例说明

    1. 利用多线程实现如下需求: 写两个线程,一个线程打印1~52,另一个线程打印A~Z,打印顺序是12A34B...5152Z: 2. 使用synchronized 实现 public class T ...

  4. Java多线程synchronized同步

    非线程安全问题 “非线程安全”问题存在于“实例变量”中,如果是方法内部的私有变量,则不存在“非线程问题”.也即是说,方法中的变量永远是线程安全的. 如果多个线程共同访问1个对象中的实例变量,则可能线程 ...

  5. JAVA多线程synchronized详解

    Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 当两个并发线程访问同一个对象object中的这个synchronized(this)同 ...

  6. java多线程-synchronized

    一.线程安全问题 多线程操作各自线程创建的资源的时候,不存在线程安全问题.但多线程操作同一个资源的时候就会出现线程安全问题.下例为两个线程操作同一个name资源时发生的问题. class TestSy ...

  7. java 多线程 Synchronized方法和方法块 synchronized(this)和synchronized(object)的理解

    synchronized 关键字,它包括两种用法:synchronized 方法和 synchronized 块. 1. synchronized 方法:通过在方法声明中加入 synchronized ...

  8. Java 多线程 - Synchronized关键字

    目录 1-Synchronized 关键字概述 2- Synchronized关键字作用域 3- Synchronized 原理(反编译指令解释) 正文 1-Synchronized 关键字概述 由于 ...

  9. java多线程:synchronized和lock比较浅析

    转载:http://www.toutiao.com/a6392135944652587266/?tt_from=weixin&utm_campaign=client_share&app ...

随机推荐

  1. swift 集合类型(一)

    基本的数组结构Array: var shoppingList: String[] = ["Eggs", "Milk"] 这个shoppingList和传统意义上 ...

  2. 微软职位内部推荐-Software Development Engineer

    微软近期Open的职位: Job Title: Software Development Engineer Work Location: Suzhou, China The Office 365 Co ...

  3. Html5 Egret游戏开发 成语大挑战(三)开始界面

    本篇需要在前面的素材准备完毕,才可以开始,使用egret的eui结合代码编辑,快速完成基本的界面搭建,这里写的可能比较细,目的是减少大家对于其中一些操作疑问,我去掉了很多无用的步骤,以最精简的流程来完 ...

  4. 代码整洁--使用CodeMaid自动程序排版

    在项目开发的过程中,如果只是验证命名规则.而没有统一程序排版,项目中很容易就会出现类似下列范例的程序代码产出.这样的产出,虽然能够正常地提供项目功能.并且符合微软的命名规则,但是因为程序排版凌乱的问题 ...

  5. C语言 自动修改文件名小程序

    #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <stri ...

  6. 利用windbg查找dictionary导致IIS占CPU100%案例分析(一)

    一.背景 先说下windbg使用场景.各位coder在工作中或多或少都会遇到下面四种情况 1.本地代码好好的,放服务器上运行一段时间后,IIS服务突然占用 w3wp.exe CPU突然100% ,不得 ...

  7. 【位运算经典应用】 N皇后问题

    说到位运算的经典应用,不得不说N皇后问题. 学过程序设计的都知道N皇后问题,没听过也没关系.很简单,最传统的的N皇后问题是这个样子的,给你一个n * n大小的board,让你放n个皇后(国际象棋),要 ...

  8. [Python]新手写爬虫全过程(已完成)

    今天早上起来,第一件事情就是理一理今天该做的事情,瞬间get到任务,写一个只用python字符串内建函数的爬虫,定义为v1.0,开发中的版本号定义为v0.x.数据存放?这个是一个练手的玩具,就写在tx ...

  9. meta标签大全

    meta标签大全 <!--     x-ua-compatible(浏览器兼容模式)     仅对IE8+以效     告诉浏览器以什么版本的IE的兼容模式来显示网页     <meta ...

  10. [codeforces 519E]E. A and B and Lecture Rooms(树上倍增)

    题目:http://codeforces.com/problemset/problem/519/E 题意:给你一个n个点的树,有m个询问(x,y),对于每个询问回答树上有多少个点和x,y点的距离相等 ...