并发编程之Synchronized原理

一、基本使用

   1.Synchronized的作用。

  • 原子性:确保线程互斥的访问同步代码;
  • 可见性:保证共享变量的修改能够及时可见,其实是通过Java内存模型中的 “对一个变量unlock操作之前,必须要同步到主内存中;如果对一个变量进行lock操作,则将会清空工作内存中此变量的值,在执行引擎使用此变量前,需要重新从主内存中load操作或assign操作初始化变量值” 来保证的;
  • 有序性:有效解决重排序问题,即 “一个unlock操作先行发生(happen-before)于后面对同一个锁的lock操作”
   2.Synchronized的三种用法。Synchronized可以把任何一个非null对象作为"锁"。
  • 当synchronized作用在实例方法时,监视器锁(monitor)便是对象实例(this)
  • 当synchronized作用在静态方法时,监视器锁(monitor)便是对象的Class实例,因为Class数据存在于永久代,因此静态方法锁相当于该类的一个全局锁
  • 当synchronized作用在某一个对象实例时,监视器锁(monitor)便是括号括起来的对象实例。

  3.synchronized与Lock的区别

  1.首先synchronized是java内置关键字,在jvm层面,Lock是个java类;

  2.synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁;

  3.synchronized会自动释放锁(a 线程执行完同步代码会释放锁 ;b 线程执行过程中发生异常会释放锁),Lock需在finally中手工释放锁(unlock()方法释放锁),否则容易造成线程死锁;

  4.用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去,而Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束了;

  5.synchronized的锁可重入、不可中断、非公平,而Lock锁可重入、可判断、可公平(两者皆可)

  6.Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。

类别synchronizedLock
存在层次 Java的关键字,在jvm层面上 是一个类
锁的释放 1、以获取锁的线程执行完同步代码,释放锁 2、线程执行发生异常,jvm会让线程释放锁 在finally中必须释放锁,不然容易造成线程死锁
锁的获取 假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待 分情况而定,Lock有多个锁获取的方式,具体下面会说道,大致就是可以尝试获得锁,线程可以不用一直等待
锁状态 无法判断 可以判断
锁类型 可重入 不可中断 非公平 可重入 可判断 可公平(两者皆可)
性能 少量同步 大量同步
二、同步原理

1.数据同步需要依赖锁,那锁的同步又依赖谁?

 synchronized:在软件层面依赖JVM。

 j.u.c.Lock:在硬件层面依赖特殊的CPU指令。

2.监视器锁(monitor):每个对象都是一个监视器锁(monitor)。

  Synchronized的语义底层是通过一个monitor的对象来完成。

3.monitorenter:每个对象都是一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权。

  • 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者;
  • 如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1;
  • 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权;

4.monitorexit:执行monitorexit的线程必须是objectref所对应的monitor的所有者。指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。

三、同步概念

1.Java对象头

  对象在内存中的布局分为三块区域:对象头、实例数据和对齐填充。

  • 实例数据:存放类的属性数据信息,包括父类的属性信息;
  • 对齐填充:由于虚拟机要求 对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐;
  • 对象头Java对象头一般占有2个机器码(在32位虚拟机中,1个机器码等于4字节,也就是32bit,在64位虚拟机中,1个机器码是8个字节,也就是64bit),但是 如果对象是数组类型,则需要3个机器码,因为JVM虚拟机可以通过Java对象的元数据信息确定Java对象的大小,但是无法从数组的元数据来确认数组的大小,所以用一块来记录数组长度。

  

 2.对象头中Mark Word与线程中Lock Record

 3.监视器(Monitor)

 任何一个对象都有一个Monitor与之关联,当且一个Monitor被持有后,它将处于锁定状态。

 Synchronized在JVM里的实现都是 基于进入和退出Monitor对象来实现方法同步和代码块同步。

 3.1 MonitorEnter指令:插入在同步代码块的开始位置,当代码执行到该指令时,将会尝试获取该对象Monitor的所有权,即尝试获得该对象的锁;

 3.2 MonitorExit指令:插入在方法结束处和异常处,JVM保证每个MonitorEnter必须有对应的MonitorExit;

 参考:深入分析Synchronized原理。

原文地址:https://www.cnblogs.com/wenxiangchen/p/11340799.html