先综述个结论:

一般说的synchronized用来做多线程同步功能,其实synchronized只是提供多线程互斥,而对象的wait()和notify()方法才提供线程的同步功能。

一般说synchronized是加锁,或者说是加对象锁,其实对象锁只是synchronized在实现锁机制中的一种锁(重量锁,用这种方式互斥线程开销大所以叫重量锁,或者叫对象monitor),而synchronized的锁机制会根据线程竞争情况在运行会有偏向锁、轻量锁、对象锁,自旋锁(或自适应自旋锁)等,总之,synchronized可以认为是一个几种锁过程的封装。


原理

通常说的synchronized在方法或块上加锁,这里的锁就是对象锁(当然也可以在类上面),或者叫重量锁,在JVM中又叫对象监视器(Monitor),就是对象来监视线程的互斥。

先来回顾一下对象在堆里的逻辑结构:

对象在内存中的结构看这里》》

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

其中Tag的2bit用来显示锁类型。通常我们说synchronized的对象锁,就是这里Tag=10时的monitor对象,这里的Monitor address就是这个monitor对象(就是重量锁)的地址。

当多个线程同时请求synchronized方法或块时,monitor会设置几个虚拟逻辑数据结构来管理这些多线程。下图是简化了的管理结构。

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

同时再看看线程的状态图

Blocked就是阻塞状态。

wait()和sleep()最大的不同在于wait()会释放对象锁,而sleep()不会!wait、sleep、yield区别如下:

似乎讲到这里,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是一个典型的非公平锁方案,而且没法做成公平锁。

public String name;
public void in()
{
synchronized(this)//
{
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} }
System.out.println(System.currentTimeMillis());
} public void out()
{
synchronized(this)//
{
this.notifyAll();
}
}

synchronized、锁、多线程同步的原理是咋样的更多相关文章

  1. synchronized锁机制的实现原理

    Synchronized 锁机制的实现原理 Synchronized是Java种用于进行同步的关键字,synchronized的底层使用的是锁机制实现的同步.在Java中的每一个对象都可以作为锁. J ...

  2. iOS:多线程同步加锁的简单介绍

    多线程同步加锁主要方式有3种:NSLock(普通锁).NSCondition(状态锁).synchronized同步代码块 还有少用的NSRecursiveLock(递归锁).NSConditionL ...

  3. 老掉牙的 synchronized 锁优化,一次给你讲清楚!

    我们都知道 synchronized 关键字能实现线程安全,但是你知道这背后的原理是什么吗?今天我们就来讲一讲 synchronized 实现线程同步背后的原因,以及相关的锁优化策略吧. synchr ...

  4. JAVA之旅(十三)——线程的安全性,synchronized关键字,多线程同步代码块,同步函数,同步函数的锁是this

    JAVA之旅(十三)--线程的安全性,synchronized关键字,多线程同步代码块,同步函数,同步函数的锁是this 我们继续上个篇幅接着讲线程的知识点 一.线程的安全性 当我们开启四个窗口(线程 ...

  5. Java多线程---同步与锁

    一,线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏. 二.同步和锁定 1.锁的原理 Java中每个对象都有一个内置锁. 当程序运行到非静态的synchronized同步方法上时,自动 ...

  6. Synchronized锁性能优化偏向锁轻量级锁升级 多线程中篇(五)

    不止一次的提到过,synchronized是Java内置的机制,是JVM层面的,而Lock则是接口,是JDK层面的 尽管最初synchronized的性能效率比较差,但是随着版本的升级,synchro ...

  7. Java多线程5:Synchronized锁机制

    一.前言 在多线程中,有时会出现多个线程对同一个对象的变量进行并发访问的情形,如果不做正确的同步处理,那么产生的后果就是“脏读”,也就是获取到的数据其实是被修改过的. 二.引入Synchronized ...

  8. 015-线程同步-synchronized几种加锁方式、Java对象头和Monitor、Mutex Lock、JDK1.6对synchronized锁的优化实现

    一.synchronized概述基本使用 为确保共享变量不会出现并发问题,通常会对修改共享变量的代码块用synchronized加锁,确保同一时刻只有一个线程在修改共享变量,从而避免并发问题. syn ...

  9. 关于synchronized和ReentrantLock之多线程同步详解

    一.线程同步问题的产生及解决方案 问题的产生: Java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突. 如下例:假设有一个卖票 ...

随机推荐

  1. 时间序列分析工具箱——tibbletime

    目录 时间序列分析工具箱--tibbletime tibbletime 的用途 加载包 数据 教程:tibbletime 初始化一个 tbl_time 对象 时间序列函数 翻译自<Demo We ...

  2. Python连接MySQL数据库执行sql语句时的参数问题

    由于工作需要,今天写了一个Python小脚本,其中需要连接MySQL数据库,在执行sql命令时需要传递参数,结果出问题了.在网上查了一下,发现有以下几种方式传递参数: 一.直接把sql查询语句完整写入 ...

  3. 20155327 李百乾 Exp4 恶意代码分析

    20155327 李百乾 Exp4 恶意代码分析 基础问题回答 (1)如果在工作中怀疑一台主机上有恶意代码,但只是猜想,所以想监控下系统一天天的到底在干些什么.请设计下你想监控的操作有哪些,用什么方法 ...

  4. 20145209刘一阳《JAVA程序设计》第六周课堂测试

    第六周课堂测试 1.现有以下代码,哪些选项插入到第5行可以通过编译?(BDF) 1.import java.util.*; 2. 3.Class FindStuff { 4.public static ...

  5. OracleLinux安装说明

    磨砺技术珠矶,践行数据之道,追求卓越价值 回到上一级页面: PostgreSQL杂记页     回到顶级页面:PostgreSQL索引页 [作者 高健@博客园  luckyjackgao@gmail. ...

  6. P5231 [JSOI2012]玄武密码

    P5231 [JSOI2012]玄武密码 链接 分析: 首先对所有询问串建立AC自动机,然后扫描一遍母串,在AC自动机上走,没走到一个点,标记这个点走过了,并且它的fail树上的祖先节点也可以访问到( ...

  7. BootStap学习笔记(2)

    学习该内容之前可能会用到的内容: css属性Font-Weight:如果数字为700就是加粗的.或者更粗的为bolder,更细的是lighter. html Cite标签定义文档的引用,默认字体以斜体 ...

  8. c# url链接转成二维码图片,再转成byte[]二进制流,输出到前段ajax

    需要用到的 dll 添加引用 代码: //获取配置文件设置的url string urllink = ConfigurationManager.AppSettings["urllink&qu ...

  9. 网格系统-bootStrap4常用CSS笔记

    .row 定义一行 .col 均分列数,最多一行12列.每列左右间隙各15px .col-{1到12} 定义在所有屏幕下的列宽 .col-{sm|md|lg|xl}-{1到12} 定义在指定屏幕下该列 ...

  10. Datawhale MySQL 训练营 Task6 实战项目

    作业 项目十:行程和用户(难度:困难) Trips 表中存所有出租车的行程信息.每段行程有唯一键 Id,Client_Id 和 Driver_Id 是 Users 表中 Users_Id 的外键.St ...