专栏首页EffectiveCodingJava Concurrent synchronized 使用&原理

Java Concurrent synchronized 使用&原理

sychronized 用法

sychronized 是Java语法层面的同步策略,可以用来修饰instance变量object reference(对象引用)、static函数class literals(类名称字面常量)。 1、当非static 元素被sychronized修饰时,当前线程都会取得该对象锁,该对象的其他线程均无法访问任何被sychronized修饰的变量或方法。即一个类如果有n个方法被sychronized修饰时,a线程取得对象锁之后,其他线程除a线程正在使用的方法无法使用外,其他需要对象锁的方法均无法使用。即一个对象仅有一个对象锁,一个线程取得后,其他线程都无法获得,其他线程都要阻塞。 2、不同的对象实例的 synchronized方法是不相干扰的。 3、当static 元素被sychronize修饰时,可以防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。 注意:synchronized都是会阻塞线程的,就是说会发生上下文切换,从用户态切换到内核态,所以由sychronized实现对象锁代价较高(新的JDK版本已经优化的较好,但这种方式代价仍然不小),并且使用sychronized涉及对象锁如果在两个以上很容易造成死锁,谨慎使用同步策略,避免无谓的取锁。 很显然sychronized是一种独占锁,也就是悲观锁,默认一定会发生资源争用,所以每次都默认取锁。

sychronized 原理

同步是使用monitorentermonitorexit指令实现的,monitorenter尝试获取对象的锁,如果该对象没被锁定或者当前线程已经获取了锁,则把锁的计数器+1,同样monitorexit把锁的计数器-1。所以synchronized对于同一个线程是可重入的。 Sychronized是基于监视器实现的,对象锁为对象实例的监视器,class锁为class监视器,锁住的其实对应的监视器。 当出现n个线程请求对象监视器时,监视器会设置这样几个状态来表示所请求的线程。 contentionlist:存放所有线程的竞争队列; entrylist:存放复合条件的候选竞争队列; ondeck:任何时刻仅有一个线程可以直接竞争锁,这个被称为ondeck。 owner:对象锁的持有者,正在执行的线程 waitlist:执行过程中,因wait方法被阻塞的线程。

image.png

首先来竞争的线程会进入contentionlist,如果entrylist为空时,从contentionlist中取得,然后依次竞争锁然后执行,被wait阻塞进入waitset或者执行完成unlock释放锁。当被阻塞线程被notify或者notifyAll唤醒时,重新进入enterylist,等待获得竞争锁的权利。 1、其中contentionlist并不是真正的queue,而是一种虚拟队列,因为ContentionList仅由Node及其next指针逻辑构成,并不存在一个Queue的数据结构,准确来说就是一个普通list,仅仅是采用FIFO操作而已,每次新节点时都会在队首进行,通过CAS操作(后续会有单独篇幅介绍CAS)改变第一个节点的的指针为新增节点,同时设置新增节点的next指向后续节点,而取得操作则发生在队尾,且只有一个线程会去,所以这里的CAS操作不会产生ABA问题。显然,该结构其实是个Lock-Free的队列。 ps: lock-free:需要取得锁的线程在有限步骤或时间内就可以成功,多数线程都会成功,一些可能失败。 wait-free:需要取得锁的线程在有限步骤或时间内就可以成功,任意线程都会成功,语义更加强烈。 2、entrylist也是等待队列,因为contentionlist会被线程并发访问,为了降低对contentionlist队尾争用,所以产生了entrylist结构。 3、当entry为空时,会将contention中的线程迁移到entrylist,并且会指定其中的某个线程(通常是第一个)为ondeck,并不是直接给他锁,而是给它竞争锁的权利,ondeck需要重新竞争锁,这样牺牲了一定的公平性,但极大的提高了整体吞吐量,Hotspot中把OnDeck的选择行为称之为“竞争切换”。 ps:阻塞操作由操作系统完成,在Linxu下通过pthread_mutex_lock函数实现。

自旋锁、自适应性自旋锁

说到这里,sychronized主流程已经清楚了,但还有两个概念,自旋锁、自适应性自旋锁。 1、自旋锁就是monitor并不把线程阻塞放入排队队列,而是去执行空转,空转后看看是否锁已释放并直接进行竞争获得cpu,如果竞争不到继续自旋,循环过程中线程的状态一直处于running状态。明显自旋锁使得synchronized的对象锁方式在线程之间引入了不公平。但是这样可以保证大吞吐率和执行效率。自旋锁方式省去了阻塞线程的时间和空间(队列的维护等)开销,但是长时间自旋也是很低效的。所以自旋的次数一般控制在一个范围内,例如10,50等,在超出这个范围后,线程就进入排队队列。 2、自适应自旋锁就是自旋的次数是通过JVM在运行时收集的统计信息,动态调整自旋锁的自旋次数上界。 自旋的发生时机:进入contentionlist前,先尝试自旋获得锁,若失败,再去排队。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java Concurrent Executor

    在说Executor前, 先来看一下线程创建的几种方式: 1、继承Thread类创建线程 2、 实现Runable接口创建线程 3、使用Callable和...

    邹志全
  • 线程和进程

    进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.

    邹志全
  • Java Concurrent 背景&基础概念&操作系统

    并发可能在许多刚接触编程的程序员眼中显得高大上或者多余,因为刚接触编程时不是很理解 并发的背景、意义,并且并发编程通常相对于串行执行的程序要复杂一些。

    邹志全
  • java基础

    当一个线程需要调用对象的wait()方法的时候,这个线程必须拥有该对象的锁,接着它就会释放这个对象锁并进入等待状态直到其他线程调用这个对象上的notify()方...

    大学里的混子
  • java面试

    当一个线程需要调用对象的wait()方法的时候,这个线程必须拥有该对象的锁,接着它就会释放这个对象锁并进入等待状态直到其他线程调用这个对象上的notify()方...

    大学里的混子
  • [nptl][mutex]老司机带你十分钟定位死锁问题

    前言: 死锁问题,几乎可以用“自古”来形容。PV原语一出,信号量嵌套使用,就伴随着死锁问题的发生。死锁类问题的解决过程,基本上就是定位到发生死锁的位置以及原因,...

    皮振伟
  • python线程笔记

    豌豆贴心提醒,本文阅读时间5分钟 来源:伯乐在线 原文:http://python.jobbole.com/87498/ 引言&动机 考虑一下...

    小小科
  • Java高并发面试题

    线程安全类: 一个类是线程安全的是指, 在多线程进行调用时,不需要额外的同步和其他协调,类的行为任然是正确的.

    用户7625070
  • Java 线程池原理与使用

    在java 中我们会一般要求创建线程必须使用线程池,因为这样可以避免资源消耗,通过重复利用已经创建的线程来降低线程创建和销毁所造成的消耗, 其次当任务到达时任务...

    用户7625070
  • Java 多线程 Thread 和 Runnable

    多线程是并行计算实现的方式, 但是在单cpu中实际上没有真正的并行,只不过是多个任务通过cpu的快速轮转,产生多任务同一时间运行的错觉.而其中的任务就是进程. ...

    用户7625070

扫码关注云+社区

领取腾讯云代金券