先综述个结论:一般说的synchronized用来做多线程同步功能,其实synchronized只是提供多线程互斥,而对象的wait()和notify()方法才提供线程的同步功能。一般说synchronized是加锁,或者说是加对象锁,其实对象锁只是synchronized在实现锁机制中的一种锁(重量锁,用这种方式互斥线程开销大所以叫重量锁,或者叫对象monitor),而synchronized的锁机制会根据线程竞争情况在运行会有偏向锁、轻量锁、对象锁,自旋锁(或自适应自旋锁)等,总之,synchronized可以认为是一个几种锁过程的封装。

原理
通常说的synchronized在方法或块上加锁,这里的锁就是对象锁(当然也可以在类上面),或者叫重量锁,在JVM中又叫对象监视器(Monitor),就是对象来监视线程的互斥。
先来回顾一下对象在堆里的逻辑结构:

图1

对象头里的结构大致如此:

图2

其中Tag的2bit用来显示锁类型。通常我们说synchronized的对象锁,就是这里Tag=10时的monitor对象,这里的Monitor address就是这个monitor对象(就是重量锁)的地址。
当多个线程同时请求synchronized方法或块时,monitor会设置几个虚拟逻辑数据结构来管理这些多线程。下图是简化了的管理结构。

图3

新请求的线程会首先被加入到线程排队队列中,线程阻塞,当某个拥有锁的线程unlock之后,则排队队列里的线程竞争上岗(synchronized是不公平竞争锁,下面还会讲到)。如果运行的线程调用对象的wait()后就释放锁并进入wait线程集合那边,当调用对象的notify()或notifyall()后,wait线程就到排队那边。这是大致的逻辑。
同时再看看线程的状态图

图4

Blocked就是阻塞状态。
wait()和sleep()最大的不同在于wait()会释放对象锁,而sleep()不会!wait、sleep、yield区别如下:

图5

似乎讲到这里,synchronized锁和wait()、notify()来实现多线程同步就完成了。
但是,自旋锁或自适应自旋锁:
因为线程阻塞后进入排队队列和唤醒都需要CPU从用户态转为核心态,尤其频繁的阻塞和唤醒对CPU来说是负荷很重的工作。同时统计发现,很多对象锁的锁定状态只会持续很短的一段时间,例如一个线程切换周期,这样的话在很短的时间内阻塞线程又很快唤醒线程显然不值得,所以引入了自旋锁概念。所谓“自旋”,就monitor并不把线程阻塞放入排队队列,而是去执行一个无意义的循环,循环结束后看看是否锁已释放并直接进行竞争上岗步骤,如果竞争不到继续自旋循环,循环过程中线程的状态一直处于running状态。明显自旋锁使得synchronized的对象锁方式在线程之间引入了不公平。但是这样可以保证大吞吐率和执行效率。
不过虽然自旋锁方式省去了阻塞线程的时间和空间(队列的维护等)开销,但是长时间自旋也是很低效的。所以自旋的次数一般控制在一个范围内,例如10,50等,在超出这个范围后,线程就进入排队队列。自适应自旋锁,就是自旋的次数是通过JVM在运行时收集的统计信息,动态调整自旋锁的自旋次数上界。讲到这里似乎synchronized锁的过程更加丰满了。不过synchronized在运行过程中不是一下子就到对象锁这个级别的,它根据线程竞争情况会经过几次升级变化。这里就出现了另外几种锁。轻量锁和偏向锁
当多线程环境进入synchronized区域的线程没竞争时,JVM并不会马上创建对象锁,而是用轻量锁或偏向锁。
不过需要明确的是,轻量锁和偏向锁,都不能代替重量锁,只不过是在没有多线程竞争时,没必要用重量锁而无畏的消耗资源。但是一旦出现了多线程竞争时,synchronized区域的轻量锁或偏向锁都会立即升级为重量锁。
轻量锁或偏向锁使用的条件是进入synchronized区域时没有其他任何其他线程在使用。
这时线程t访问对象的synchronized区域时,对象头的标志位Tag状态为01,以及还有1位的偏向信息用于记录这个对象是否可用偏向锁。然后t在对象上申请轻量锁时,若偏向信息为0,表明当前对象还未加锁,或加过偏向锁(加过,注意是加过偏向锁的对象只能被同样的线程加锁,如果不同的线程想要获取锁,需要先将偏向锁升级为轻量锁,稍后会讲到),在判断对当前对象确实没有被任何其他线程锁住后,即可以在该对象上加轻量锁。
加轻量锁的过程很简单:在当前线程的栈帧(stack frame)中生成一个锁记录(lock record),这个锁记录比前面说的那个对象锁(管理线程队列的monitor)简单多了,它只是对象头的一个拷贝。然后把对象头里的tag改成00,并把这个栈帧里的lock record地址放入对象头里。若操作成功,那就完成了轻量锁操作。如果不成功,说明有线程在竞争,则需要在当前对象上生成重量锁来进行多线程同步,然后将Tag状态改为10,并生成Monitor对象(重量锁对象),对象头里也会放入Monitor对象的地址。最后将当前线程t排队队列中。
轻量锁的解锁过程也很简单就是把栈帧里刚才的那个lock record拷贝到对象头里,若替换成功,则解锁完成,若替换不成功,表示在当前线程持有锁的这段时间内,其他线程也竞争过锁,并且发生了锁升级为重量锁,这时需要去Monitor的等待队列中唤醒一个线程去重新竞争锁。
偏向锁是比轻量锁还轻量的锁机制。当synchronized区域长期都由同一个线程加锁、解锁时,jvm就用偏向锁来做,它的加锁解锁比轻量锁操作起来指令更加简化。不过一旦有其他线程使用synchronized区域,即使没有线程间竞争,也会把偏向锁升级为轻量锁,当然如果发生线程竞争就再升级为对象锁。锁的公平与不公平:公平锁是指线程获得锁的顺序按照fifo的原则,先排队的先得。非公平锁指每个线程都先要竞争锁,不管排队先后,所以后到的线程有可能无需进入等待队列直接竞争到锁。非公平锁虽然可能导致某些线程饥饿,但是锁的吞吐率是公平锁好几倍,synchronized是一个典型的非公平锁方案,而且没法做成公平锁。

java同步锁synchronized_synchronized、锁、多线程同步的原理是咋样的?相关推荐

  1. java线程条件变量_多线程同步条件变量(转载)

    最近看<UNIX环境高级编程>多线程同步,看到他举例说条件变量pthread_cond_t怎么用,愣是没有看懂,只好在网上找了份代码,跑了跑,才弄明白 #include #include ...

  2. linux 线程同步消息队列,Linux 多线程同步之消息队列

    消息队列是消息的链表,存放在内核中并有消息队列标示符标示. msgget用于创建一个新队列或打开一个现存的队列.msgsnd将新消息加入到消息队列中:每个消息包括一个long型的type:和消息缓存: ...

  3. Delphi中实现多线程同步查询

    优秀的数据库应用应当充分考虑数据库访问的速度问题.通常可以通过优化数据库.优化 查询语句.分页查询等途径收到明显的效果.即使是这样,也不可避免地会在查询时闪现一个带有 SQL符号的沙漏,即鼠标变成了查 ...

  4. Java入门需要了解(多线程的线程安全-二十一)

    多线程中最棘手也不容易分析的问题就是多线程处理共享数据时因为CPU在线程间随记切换而引发的各种安全问题 引发线程安全的原因 解决多线程的安全问题 同步函数 同步函数和同步代码块的区别 静态的同步函数( ...

  5. java线程同步以及对象锁和类锁解析(多线程synchronized关键字)

    一.关于线程安全 1.是什么决定的线程安全问题? 线程安全问题基本是由全局变量及静态变量引起的. 若每个线程中对全局变量.静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的:若有多个线 ...

  6. 0037 Java学习笔记-多线程-同步代码块、同步方法、同步锁

    什么是同步 在上一篇0036 Java学习笔记-多线程-创建线程的三种方式示例代码中,实现Runnable创建多条线程,输出中的结果中会有错误,比如一张票卖了两次,有的票没卖的情况,因为线程对象被多条 ...

  7. Java多线程之线程同步机制(锁,线程池等等)

    Java多线程之线程同步机制 一.概念 1.并发 2.起因 3.缺点 二.三大不安全案例 1.样例一(模拟买票场景) 2.样例二(模拟取钱场景) 3.样例三(模拟集合) 三.同步方法及同步块 1.同步 ...

  8. 使用线程锁(lock)实现线程同步_一文搞懂Java多线程使用方式、实现原理以及常见面试题...

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutorial 喜欢的话麻烦点下 ...

  9. Java:多线程(同步死锁、锁原子变量、线程通信、线程池)

    5,同步和死锁 5.1,线程同步 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象: 修饰一个方法,被修饰的方法称为同步方法,其作用 ...

最新文章

  1. Spring4MVC 请求参数映射和Content-type
  2. 易企秀如何生成图片_易企秀可以导出图片吗
  3. springMVC---处理模型数据方法 *
  4. c#获取网页源码全解
  5. cvAddWeighted 进行图片融合
  6. windows安装ubuntu系统的注意事项小记
  7. Jpa 注解详解 映射详解 一对多 多对一
  8. 双动道岔计算机控制系统,车站信号自动控制习题.doc
  9. linux和手机通讯,在Linux的系统下使用红外进行手机通讯
  10. java8/jdk1.8 官网下载地址
  11. 证明调和级数发散的方法
  12. Ae 内置效果控件(合集)
  13. solrCloud配置
  14. 微信小程序原生表格组件
  15. iOS 汉字转换为拼音
  16. Win10无法彻底删除蓝牙设备,无线蓝牙鼠标连接后不能删除
  17. 一位IT新人对工作计划的心得体会
  18. CAD2020版本设置为经典模式
  19. 【chrome】模拟微信浏览器
  20. 在Windows系统和Linux系统中,如何打造一个好终端?

热门文章

  1. Android学习笔记----ArcGIS在线地图服务(Android API)坐标纠偏
  2. C# GridView单元格合并.
  3. 从文本分类问题中的特征词选择算法追踪如何将数学知识,数学理论迁移到实际工程中去...
  4. 分层结构,协议,接口,服务
  5. java字符串数组排序_在Java中对字符串数组进行排序
  6. 龙贝格方法c语言,龙贝格算法
  7. 对new int[]()的理解(转载)
  8. java中一个线程最小优先数_Java线程的优先级
  9. win10下TensorFlow-GPU安装(GTX1660+CUDA10+CUDNN7.4)
  10. 'chcp' 不是内部或外部命令,也不是可运行的程序