在理解synchronized原理之前,先理解什么是内置锁。
多线程的锁,其实本质上就是给一块内存空间的访问添加访问权限,因为Java中是没有办法直接对某一块内存进行操作的,又因为Java是面向对象的语言,万物皆对象,所以具体的体现就是某一个对象承担锁的功能,每一个对象都可以是一个锁。
内置锁,它是一个互斥锁,即最多只有一个线程能够获得该锁。使用方式就是使用 synchronized 关键字,synchronized 方法或者 synchronized 代码块。当线程A获得锁后才能执行方法,而其他线程则处于等待或者阻塞,直到A线程释放锁,若A线程不释放锁,其他线程则一直等待,保证了被synchronized修饰的方法或者代码块相当于原子性操作,保证了线程安全性。
1、当synchronized作用于普通(实例)方法时,内置锁对象是this(当前类的实例);
2、当synchronized作用于静态方法时,内置锁对象是当前类的Class字节码对象(类名.class);
3、当synchronized作用于代码块时,锁对象是synchronized(obj){}中的这个obj,obj只要是对象即可。
从jvm角度看synchronized
同步代码块中,进入同步代码块字节码为monitorenter,离开同步代码块字节码为monitorexit。
但在同步方法中并没有使用这两个字节码。
从操作系统角度看synchronized
Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现的。但是监视器锁本质又是依赖于底层的操作系统的Mutex Lock(互斥锁)来实现的。操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这也就是为什么Synchronized效率低的原因。因此,这种依赖于操作系统Mutex Lock所实现的锁我们称之为“重量级锁”。
问:任何对象都可以作为锁,那么锁信息又存在对象的什么地方呢?
答:存在于对象头中,关于java对象的结构,我之前写了一篇文章《java对象的结构》
synchronized的执行过程:
1. 检测Mark Word里面是不是当前线程的ID,如果是,表示当前线程处于偏向锁
2. 如果不是,则使用CAS将当前线程的ID替换Mard Word,如果成功则表示当前线程获得偏向锁,置偏向标志位1
3. 如果失败,则说明发生竞争,撤销偏向锁,进而升级为轻量级锁。
4. 当前线程使用CAS将对象头的Mark Word替换为锁记录指针,如果成功,当前线程获得锁
5. 如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁。
6. 如果自旋成功则依然处于轻量级状态。
7. 如果自旋失败,则升级为重量级锁。
synchronized除了保证线程之间的互斥以外,还能保证变量的可见性(保证变量可见性的前提是,需要保证多个线程拿到的是同一把锁,否则无法保证)