synchronized

145 阅读5分钟

一、一些概念

同步:是指一个线程等待其他线程运行到某个点
互斥:是指同一时刻只能有一个线程执行临界区的代码

二、线程安全的解决方案

阻塞式的解决方案:synchronized,Lock
非阻塞式的解决方案:原子变量Atomic,底层是自旋

三、synchronized使用方法

可以修饰静态方法、非静态方法、代码块
1.修饰静态方法:锁住的是Class对象
public synchronized void method(){}
2.修饰普通方法:锁住的是类的实例对象
public static synchronized void method(){}
3.修饰代码块:
  (1)synchronized(this){}:锁住的是类的实例对象
  (2)synchronized(Main.class){}:锁住的是Class对象

四、synchronized底层原理

1.版本变化

jdk1.5以前,Synchronized是jvm内置锁,基于monitor实现的,底层依赖操作系统的互斥原语mutex互斥量来实现
的,它是一个重量级锁,因为涉及到操作系统底层的api调用,所以会有用户态到内核态的切换,性能低。
jdk1.5以后做了优化,例如锁粗化,锁消除,偏向锁,轻量级锁,自适应自旋等操作来减少锁操作的开销。

2.字节码层面

编译字节码可以看到,同步代码块是通过monitorenter和monitorexit来实现的,这两个指令的执行是jvm通过调用
操作系统的互斥原语mutex来实现的,被阻塞的线程会被挂起,等待重新调度,会导致用户态和内核态之间来回切换。

3.monitor(管程/监视器)&MESA模型

(1)管程是管理共享变量以及对共享变量操作的过程,让他们支持并发。
在java中,Synchronized关键字、wait()、notify()、notifyAll()就是实现管程的方式。
(2)MESA模型
管程实现的模型:MESA模型,是一种使用比较广泛的实现管程的模型。
MESA模型的基本概念:
入口等待队列:多个线程试图进入时,排队,只允许一个线程进入,其他线程排队。
条件变量&条件变量等待队列:解决线程同步问题,即满足xx条件wait,满足xx条件notify
Synchronized对MESA模型做了简化,条件变量只有一个。


4.Synchronized加锁加在对象上,锁对象是如何记录锁状态的?

(1)对象的内存布局
   对象在内存中存储的布局可以分为三块区域,对象头,实例数据,对齐填充。
   1)对象头,对象头又分为三个部分,markword,Klass指针,数组长度
     markword:包含hashcode,gc分代年龄,锁状态标记,线程持有的锁,偏向id等。
     64位系统的这部分长度为8字节。
     Klass指针:对象指向它的类元数据的指针,jvm通过这个指针来确定这个对象是哪个类的实例。
     jdk1.8默认开启指针压缩,长度为4字节,不开启指针压缩长度为8字节。
     数组长度:只有数组对象才有,长度为4字节。
   2)实例数据
     存放类的属性数据信息,包括父类的属性信息。
   3)对齐填充
     jvm要求对象必须是8字节的整数倍,填充数据不是必须的,主要是为了字节对齐。
(2)锁状态是记录在锁对象的对象头的markword中的,每个对象都可以作为锁对象。
(3)markword是如何记录锁状态的?(了解)
   64位系统,末尾值的含义
   无锁:001
   偏向锁:101
   轻量级锁:00
   重量级锁:10
   GC标记:11

5.锁的类型和升级

(1)锁的类型
   1)偏向锁
   如果只有一个线程执行这个同步块,那么此时synchronized加的就是偏向锁。
   2)如果一段时间内有多个线程执行这个同步块,但是竞争不是很激烈,加锁解锁时间很快,线程可以交替执行,
   那么此时synchronized加的就是轻量级锁(实现方式就是自旋,自旋的次数很少就能获得锁,表示竞争不激烈,
   则此时是轻量级锁。如果达到一定的时间和次数还不能获得锁,表示竞争很激烈,此时就是重量级锁)。
   3)如果竞争很激烈,那么锁就会膨胀为重量级锁,即通过操作系统维护mutex互斥量,用户态和内核态切换,
   成本很高。
   4)偏向锁和轻量级锁操作的都是锁对象的markword,只在用户态做操作,而重量级锁是用户态和内核态切换,
   效率很低。 
(2)锁的升级
   锁的升级不是无锁-->偏向锁-->轻量级锁-->重量级锁这样顺序升级的。
   而是,无锁升级为偏向锁,然后偏向锁撤销,变为无锁,再升级为轻量级锁或者直接重量级锁。
   锁的膨胀升级是不可逆的。

5.new Object()在内存中占几个字节?

占用16个字节。
对象头占用8字节+Klass指针占用4字节+空对象没有实例数据占用0字节+对齐填充占用4字节=16字节

五、wait和sleep的区别

注意:wait使用的时候,需要注意虚假唤醒。
什么是虚假唤醒。
if(条件不满足){
    wait();
}
在这里使用if判断条件不满足,就会造成虚假唤醒,即唤醒后还是不满足条件,但是需要继续wait(),这里就需要将
if改为while避免虚假唤醒。

六、notify和notifyAll分别什么时候使用

满足以下三个条件时,可以使用notify,其余情况尽量使用notifyAll。
1.所有等待线程拥有相同的等待条件。
2.所有等待线程被唤醒后,执行相同的操作。
3.只需要唤醒一个线程。

七、Synchronized和Lock的区别

八、写一下Lock的实现,condition怎么用的

看到了33分钟