synchronized 是 Java 中的关键字,是利用锁的机制来实现同步的。html
锁机制有以下两种特性:java
互斥性:即在同一时间只容许一个线程持有某个对象锁,经过这种特性来实现多线程中的协调机制,这样在同一时间只有一个线程对需同步的代码块(复合操做)进行访问。互斥性咱们也每每称为操做的原子性。程序员
可见性:必须确保在锁被释放以前,对共享变量所作的修改,对于随后得到该锁的另外一个线程是可见的(即在得到锁时应得到最新共享变量的值),不然另外一个线程多是在本地缓存的某个副本上继续操做从而引发不一致。编程
在 Java 中,每一个对象都会有一个 monitor 对象,这个对象其实就是 Java 对象的锁,一般会被称为“内置锁”或“对象锁”。类的对象能够有多个,因此每一个对象有其独立的对象锁,互不干扰。数组
在 Java 中,针对每一个类也有一个锁,能够称为“类锁”,类锁其实是经过对象锁实现的,即类的 Class 对象锁。每一个类只有一个 Class 对象,因此每一个类只有一个类锁。缓存
synchronized 的用法能够从两个维度上面分类:安全
synchronized 能够修饰方法和代码块数据结构
修饰代码块多线程
synchronized(this|object) {}并发
synchronized(类.class) {}
修饰方法
修饰非静态方法
修饰静态方法
获取对象锁
synchronized(this|object) {}
修饰非静态方法
获取类锁
synchronized(类.class) {}
修饰静态方法
这里根据获取的锁分类来分析 synchronized 的用法
class SyncThread implements Runnable { @Override public void run() { String threadName = Thread.currentThread().getName(); if (threadName.startsWith("A")) { async(); } else if (threadName.startsWith("B")) { sync1(); } else if (threadName.startsWith("C")) { sync2(); } } /** * 异步方法 */ private void async() { try { System.out.println(Thread.currentThread().getName() + "_Async_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "_Async_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } /** * 方法中有 synchronized(this|object) {} 同步代码块 */ private void sync1() { System.out.println(Thread.currentThread().getName() + "_Sync1: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); synchronized (this) { try { System.out.println(Thread.currentThread().getName() + "_Sync1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "_Sync1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * synchronized 修饰非静态方法 */ private synchronized void sync2() { System.out.println(Thread.currentThread().getName() + "_Sync2: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); try { System.out.println(Thread.currentThread().getName() + "_Sync2_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "_Sync2_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } }复制代码
public class SyncTest { public static void main(String... args) { SyncThread syncThread = new SyncThread(); Thread A_thread1 = new Thread(syncThread, "A_thread1"); Thread A_thread2 = new Thread(syncThread, "A_thread2"); Thread B_thread1 = new Thread(syncThread, "B_thread1"); Thread B_thread2 = new Thread(syncThread, "B_thread2"); Thread C_thread1 = new Thread(syncThread, "C_thread1"); Thread C_thread2 = new Thread(syncThread, "C_thread2"); A_thread1.start(); A_thread2.start(); B_thread1.start(); B_thread2.start(); C_thread1.start(); C_thread2.start(); } }复制代码
B_thread2_Sync1: 14:44:20 A_thread1_Async_Start: 14:44:20 B_thread1_Sync1: 14:44:20 C_thread1_Sync2: 14:44:20 A_thread2_Async_Start: 14:44:20 C_thread1_Sync2_Start: 14:44:20 A_thread1_Async_End: 14:44:22 A_thread2_Async_End: 14:44:22 C_thread1_Sync2_End: 14:44:22 B_thread1_Sync1_Start: 14:44:22 B_thread1_Sync1_End: 14:44:24 B_thread2_Sync1_Start: 14:44:24 B_thread2_Sync1_End: 14:44:26 C_thread2_Sync2: 14:44:26 C_thread2_Sync2_Start: 14:44:26 C_thread2_Sync2_End: 14:44:28复制代码
结果分析:
A 类线程访问方法中没有同步代码块,A 类线程是异步的,因此有线程访问对象的同步代码块时,另外的线程能够访问该对象的非同步代码块:
A_thread1_Async_Start: 14:44:20 A_thread2_Async_Start: 14:44:20 A_thread1_Async_End: 14:44:22 A_thread2_Async_End: 14:44:22复制代码
B 类线程访问的方法中有同步代码块,B 类线程是同步的,一个线程在访问对象的同步代码块,另外一个访问对象的同步代码块的线程会被阻塞:
B_thread1_Sync1_Start: 14:44:22 B_thread1_Sync1_End: 14:44:24 B_thread2_Sync1_Start: 14:44:24 B_thread2_Sync1_End: 14:44:26复制代码
synchronized(this|object) {} 代码块 {} 以外的代码依然是异步的:
B_thread2_Sync1: 14:44:20 B_thread1_Sync1: 14:44:20复制代码
C 类线程访问的是 synchronized 修饰非静态方法,C 类线程是同步的,一个线程在访问对象的同步代方法,另外一个访问对象同步方法的线程会被阻塞:
C_thread1_Sync2_Start: 14:44:20 C_thread1_Sync2_End: 14:44:22 C_thread2_Sync2_Start: 14:44:26 C_thread2_Sync2_End: 14:44:28复制代码
synchronized 修饰非静态方法,做用范围是整个方法,因此方法中全部的代码都是同步的:
C_thread1_Sync2: 14:44:20 C_thread2_Sync2: 14:44:26复制代码
由结果可知 B 类和 C 类线程顺序执行,**类中 synchronized(this|object) {} 代码块和 synchronized 修饰非静态方法获取的锁是同一个锁,即该类的对象的对象锁。**因此 B 类线程和 C 类线程也是同步的:
B_thread1_Sync1_Start: 14:44:22 B_thread1_Sync1_End: 14:44:24 C_thread1_Sync2_Start: 14:44:20 C_thread1_Sync2_End: 14:44:22 B_thread2_Sync1_Start: 14:44:24 B_thread2_Sync1_End: 14:44:26 C_thread2_Sync2_Start: 14:44:26 C_thread2_Sync2_End: 14:44:28复制代码
public class SyncTest { public static void main(String... args) { Thread A_thread1 = new Thread(new SyncThread(), "A_thread1"); Thread A_thread2 = new Thread(new SyncThread(), "A_thread2"); Thread B_thread1 = new Thread(new SyncThread(), "B_thread1"); Thread B_thread2 = new Thread(new SyncThread(), "B_thread2"); Thread C_thread1 = new Thread(new SyncThread(), "C_thread1"); Thread C_thread2 = new Thread(new SyncThread(), "C_thread2"); A_thread1.start(); A_thread2.start(); B_thread1.start(); B_thread2.start(); C_thread1.start(); C_thread2.start(); } }复制代码
A_thread2_Async_Start: 15:01:34 C_thread2_Sync2: 15:01:34 B_thread2_Sync1: 15:01:34 C_thread1_Sync2: 15:01:34 B_thread2_Sync1_Start: 15:01:34 B_thread1_Sync1: 15:01:34 C_thread1_Sync2_Start: 15:01:34 A_thread1_Async_Start: 15:01:34 C_thread2_Sync2_Start: 15:01:34 B_thread1_Sync1_Start: 15:01:34 C_thread1_Sync2_End: 15:01:36 A_thread1_Async_End: 15:01:36 C_thread2_Sync2_End: 15:01:36 B_thread2_Sync1_End: 15:01:36 B_thread1_Sync1_End: 15:01:36 A_thread2_Async_End: 15:01:36复制代码
结果分析:
class SyncThread implements Runnable { @Override public void run() { String threadName = Thread.currentThread().getName(); if (threadName.startsWith("A")) { async(); } else if (threadName.startsWith("B")) { sync1(); } else if (threadName.startsWith("C")) { sync2(); } } /** * 异步方法 */ private void async() { try { System.out.println(Thread.currentThread().getName() + "_Async_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "_Async_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } /** * 方法中有 synchronized(类.class) {} 同步代码块 */ private void sync1() { System.out.println(Thread.currentThread().getName() + "_Sync1: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); synchronized (SyncThread.class) { try { System.out.println(Thread.currentThread().getName() + "_Sync1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "_Sync1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } } /** * synchronized 修饰静态方法 */ private synchronized static void sync2() { System.out.println(Thread.currentThread().getName() + "_Sync2: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); try { System.out.println(Thread.currentThread().getName() + "_Sync2_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "_Sync2_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } }复制代码
public class SyncTest { public static void main(String... args) { SyncThread syncThread = new SyncThread(); Thread A_thread1 = new Thread(syncThread, "A_thread1"); Thread A_thread2 = new Thread(syncThread, "A_thread2"); Thread B_thread1 = new Thread(syncThread, "B_thread1"); Thread B_thread2 = new Thread(syncThread, "B_thread2"); Thread C_thread1 = new Thread(syncThread, "C_thread1"); Thread C_thread2 = new Thread(syncThread, "C_thread2"); A_thread1.start(); A_thread2.start(); B_thread1.start(); B_thread2.start(); C_thread1.start(); C_thread2.start(); } }复制代码
B_thread1_Sync1: 15:08:13 C_thread1_Sync2: 15:08:13 B_thread2_Sync1: 15:08:13 A_thread1_Async_Start: 15:08:13 C_thread1_Sync2_Start: 15:08:13 A_thread2_Async_Start: 15:08:13 C_thread1_Sync2_End: 15:08:15 A_thread2_Async_End: 15:08:15 A_thread1_Async_End: 15:08:15 B_thread2_Sync1_Start: 15:08:15 B_thread2_Sync1_End: 15:08:17 B_thread1_Sync1_Start: 15:08:17 B_thread1_Sync1_End: 15:08:19 C_thread2_Sync2: 15:08:19 C_thread2_Sync2_Start: 15:08:19 C_thread2_Sync2_End: 15:08:21复制代码
结果分析:
public class SyncTest { public static void main(String... args) { Thread A_thread1 = new Thread(new SyncThread(), "A_thread1"); Thread A_thread2 = new Thread(new SyncThread(), "A_thread2"); Thread B_thread1 = new Thread(new SyncThread(), "B_thread1"); Thread B_thread2 = new Thread(new SyncThread(), "B_thread2"); Thread C_thread1 = new Thread(new SyncThread(), "C_thread1"); Thread C_thread2 = new Thread(new SyncThread(), "C_thread2"); A_thread1.start(); A_thread2.start(); B_thread1.start(); B_thread2.start(); C_thread1.start(); C_thread2.start(); } }复制代码
A_thread2_Async_Start: 15:17:28 B_thread2_Sync1: 15:17:28 A_thread1_Async_Start: 15:17:28 B_thread1_Sync1: 15:17:28 C_thread1_Sync2: 15:17:28 C_thread1_Sync2_Start: 15:17:28 C_thread1_Sync2_End: 15:17:30 A_thread2_Async_End: 15:17:30 B_thread1_Sync1_Start: 15:17:30 A_thread1_Async_End: 15:17:30 B_thread1_Sync1_End: 15:17:32 B_thread2_Sync1_Start: 15:17:32 B_thread2_Sync1_End: 15:17:34 C_thread2_Sync2: 15:17:34 C_thread2_Sync2_Start: 15:17:34 C_thread2_Sync2_End: 15:17:36复制代码
结果分析:
class SyncThread implements Runnable { @Override public void run() { String threadName = Thread.currentThread().getName(); if (threadName.startsWith("A")) { async(); } else if (threadName.startsWith("B")) { sync1(); } else if (threadName.startsWith("C")) { sync2(); } } /** * 异步方法 */ private void async() { try { System.out.println(Thread.currentThread().getName() + "_Async_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "_Async_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } /** * synchronized 修饰非静态方法 */ private synchronized void sync1() { try { System.out.println(Thread.currentThread().getName() + "_Sync1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "_Sync1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } /** * synchronized 修饰静态方法 */ private synchronized static void sync2() { try { System.out.println(Thread.currentThread().getName() + "_Sync2_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + "_Sync2_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date())); } catch (InterruptedException e) { e.printStackTrace(); } } }复制代码
public class SyncTest { public static void main(String... args) { Thread B_thread1 = new Thread(syncThread, "B_thread1"); Thread C_thread1 = new Thread(syncThread, "C_thread1"); B_thread1.start(); C_thread1.start(); } }复制代码
B_thread1_Sync1_Start: 15:35:21 C_thread1_Sync2_Start: 15:35:21 B_thread1_Sync1_End: 15:35:23 C_thread1_Sync2_End: 15:35:23复制代码
运行结果分析:
synchronized关键字不能继承。
对于父类中的 synchronized 修饰方法,子类在覆盖该方法时,默认状况下不是同步的,必须显示的使用 synchronized 关键字修饰才行。
在定义接口方法时不能使用synchronized关键字。
构造方法不能使用synchronized关键字,但可使用synchronized代码块来进行同步。
探索synchronized的实现机制、Java是如何对它进行了优化、锁优化机制、锁的存储结构和升级过程;
当一个线程访问同步代码块时,它首先是须要获得锁才能执行同步代码,当退出或者抛出异常时必需要释放锁,那么它是如何来实现这个机制的呢?咱们先看一段简单的代码:
public class SynchronizedTest {
public synchronized void test1(){
}
public void test2(){
synchronized (this){
}
}
}
利用javap工具查看生成的class文件信息来分析Synchronize的实现
从上面能够看出,同步代码块是使用monitorenter和monitorexit指令实现的,同步方法(在这看不出来须要看JVM底层实现)依靠的是方法修饰符上的ACC_SYNCHRONIZED实现。
同步代码块:monitorenter指令插入到同步代码块的开始位置,monitorexit指令插入到同步代码块的结束位置,JVM须要保证每个monitorenter都有一个monitorexit与之相对应。任何对象都有一个monitor与之相关联,当且一个monitor被持有以后,他将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor全部权,即尝试获取对象的锁;
同步方法:synchronized方法则会被翻译成普通的方法调用和返回指令如:invokevirtual、areturn指令,在VM字节码层面并无任何特别的指令来实现被synchronized修饰的方法,而是在Class文件的方法表中将该方法的access_flags字段中的synchronized标志位置1,表示该方法是同步方法并使用调用该方法的对象或该方法所属的Class在JVM的内部对象表示Klass作为锁对象。(摘自:http://www.cnblogs.com/javaminer/p/3889023.html)
原文连接:https://blog.csdn.net/chenssy/article/details/54883355
下面咱们来继续分析,可是在深刻以前咱们须要了解两个重要的概念:Java对象头,Monitor。
Java对象头、monitor
Java对象头和monitor是实现synchronized的基础!下面就这两个概念来作详细介绍。
Java对象头
synchronized用的锁是存在Java对象头里的,那么什么是Java对象头呢?Hotspot虚拟机的对象头主要包括两部分数据:Mark Word(标记字段)、Klass Pointer(类型指针)。其中Klass Point是是对象指向它的类元数据的指针,虚拟机经过这个指针来肯定这个对象是哪一个类的实例,Mark Word用于存储对象自身的运行时数据,它是实现轻量级锁和偏向锁的关键,因此下面将重点阐述
Mark Word。
Mark Word用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等。Java对象头通常占有两个机器码(在32位虚拟机中,1个机器码等于4字节,也就是32bit),可是若是对象是数组类型,则须要三个机器码,由于JVM虚拟机能够经过Java对象的元数据信息肯定Java对象的大小,可是没法从数组的元数据来确认数组的大小,因此用一块来记录数组长度。下图是Java对象头的存储结构(32位虚拟机):
对象头信息是与对象自身定义的数据无关的额外存储成本,可是考虑到虚拟机的空间效率,Mark Word被设计成一个非固定的数据结构以便在极小的空间内存存储尽可能多的数据,它会根据对象的状态复用本身的存储空间,也就是说,Mark Word会随着程序的运行发生变化,变化状态以下(32位虚拟机):
简单介绍了Java对象头,咱们下面再看Monitor。
Monitor
什么是Monitor?咱们能够把它理解为一个同步工具,也能够描述为一种同步机制,它一般被描述为一个对象。
与一切皆对象同样,全部的Java对象是天生的Monitor,每个Java对象都有成为Monitor的潜质,由于在Java的设计中 ,每个Java对象自打娘胎里出来就带了一把看不见的锁,它叫作内部锁或者Monitor锁。
Monitor 是线程私有的数据结构,每个线程都有一个可用monitor record列表,同时还有一个全局的可用列表。每个被锁住的对象都会和一个monitor关联(对象头的MarkWord中的LockWord指向monitor的起始地址),同时monitor中有一个Owner字段存放拥有该锁的线程的惟一标识,表示该锁被这个线程占用。其结构以下:
Owner:初始时为NULL表示当前没有任何线程拥有该monitor record,当线程成功拥有该锁后保存线程惟一标识,当锁被释放时又设置为NULL;
EntryQ:关联一个系统互斥锁(semaphore),阻塞全部试图锁住monitor record失败的线程。
RcThis:表示blocked或waiting在该monitor record上的全部线程的个数。
Nest:用来实现重入锁的计数。
HashCode:保存从对象头拷贝过来的HashCode值(可能还包含GC age)。
Candidate:用来避免没必要要的阻塞或等待线程唤醒,由于每一次只有一个线程可以成功拥有锁,若是每次前一个释放锁的线程唤醒全部正在阻塞或等待的线程,会引发没必要要的上下文切换(从阻塞到就绪而后由于竞争锁失败又被阻塞)从而致使性能严重降低。Candidate只有两种可能的值0表示没有须要唤醒的线程1表示要唤醒一个继任线程来竞争锁。
摘自:Java中synchronized的实现原理与应用)
咱们知道synchronized是重量级锁,效率不怎么滴,同时这个观念也一直存在咱们脑海里,不过在jdk 1.6中对synchronize的实现进行了各类优化,使得它显得不是那么重了,那么JVM采用了那些优化手段呢?
锁优化
jdk1.6对锁的实现引入了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减小锁操做的开销。
锁主要存在四中状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁能够升级不可降级,这种策略是为了提升得到锁和释放锁的效率。
自旋锁
线程的阻塞和唤醒须要CPU从用户态转为核心态,频繁的阻塞和唤醒对CPU来讲是一件负担很重的工做,势必会给系统的并发性能带来很大的压力。同时咱们发如今许多应用上面,对象锁的锁状态只会持续很短一段时间,为了这一段很短的时间频繁地阻塞和唤醒线程是很是不值得的。因此引入自旋锁。
何谓自旋锁?
所谓自旋锁,就是让该线程等待一段时间,不会被当即挂起,看持有锁的线程是否会很快释放锁。怎么等待呢?执行一段无心义的循环便可(自旋)。
自旋等待不能替代阻塞,先不说对处理器数量的要求(多核,貌似如今没有单核的处理器了),虽然它能够避免线程切换带来的开销,可是它占用了处理器的时间。若是持有锁的线程很快就释放了锁,那么自旋的效率就很是好,反之,自旋的线程就会白白消耗掉处理的资源,它不会作任何有意义的工做,典型的占着茅坑不拉屎,这样反而会带来性能上的浪费。因此说,自旋等待的时间(自旋的次数)必需要有一个限度,若是自旋超过了定义的时间仍然没有获取到锁,则应该被挂起。
自旋锁在JDK 1.4.2中引入,默认关闭,可是可使用-XX:+UseSpinning开开启,在JDK1.6中默认开启。同时自旋的默认次数为10次,能够经过参数-XX:PreBlockSpin来调整;
若是经过参数-XX:preBlockSpin来调整自旋锁的自旋次数,会带来诸多不便。假如我将参数调整为10,可是系统不少线程都是等你刚刚退出的时候就释放了锁(假如你多自旋一两次就能够获取锁),你是否是很尴尬。因而JDK1.6引入自适应的自旋锁,让虚拟机会变得愈来愈聪明。
适应自旋锁
JDK 1.6引入了更加聪明的自旋锁,即自适应自旋锁。所谓自适应就意味着自旋的次数再也不是固定的,它是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。它怎么作呢?线程若是自旋成功了,那么下次自旋的次数会更加多,由于虚拟机认为既然上次成功了,那么这次自旋也颇有可能会再次成功,那么它就会容许自旋等待持续的次数更多。反之,若是对于某个锁,不多有自旋可以成功的,那么在之后要或者这个锁的时候自旋的次数会减小甚至省略掉自旋过程,以避免浪费处理器资源。
有了自适应自旋锁,随着程序运行和性能监控信息的不断完善,虚拟机对程序锁的情况预测会愈来愈准确,虚拟机会变得愈来愈聪明。
锁消除
为了保证数据的完整性,咱们在进行操做时须要对这部分操做进行同步控制,可是在有些状况下,JVM检测到不可能存在共享数据竞争,这是JVM会对这些同步锁进行锁消除。锁消除的依据是逃逸分析的数据支持。
若是不存在竞争,为何还须要加锁呢?因此锁消除能够节省毫无心义的请求锁的时间。变量是否逃逸,对于虚拟机来讲须要使用数据流分析来肯定,可是对于咱们程序员来讲这还不清楚么?咱们会在明明知道不存在数据竞争的代码块前加上同步吗?可是有时候程序并非咱们所想的那样?咱们虽然没有显示使用锁,可是咱们在使用一些JDK的内置API时,如StringBuffer、Vector、HashTable等,这个时候会存在隐形的加锁操做。好比StringBuffer的append()方法,Vector的add()方法:
public void vectorTest(){
Vector<String> vector = new Vector<String>();
for(int i = 0 ; i < 10 ; i++){
vector.add(i + "");
}
System.out.println(vector);
}
在运行这段代码时,JVM能够明显检测到变量vector没有逃逸出方法vectorTest()以外,因此JVM能够大胆地将vector内部的加锁操做消除。
锁粗化
咱们知道在使用同步锁的时候,须要让同步块的做用范围尽量小—仅在共享数据的实际做用域中才进行同步,这样作的目的是为了使须要同步的操做数量尽量缩小,若是存在锁竞争,那么等待锁的线程也能尽快拿到锁。
在大多数的状况下,上述观点是正确的,LZ也一直坚持着这个观点。可是若是一系列的连续加锁解锁操做,可能会致使没必要要的性能损耗,因此引入锁粗话的概念。
锁粗话概念比较好理解,就是将多个连续的加锁、解锁操做链接在一块儿,扩展成一个范围更大的锁。如上面实例:vector每次add的时候都须要加锁操做,JVM检测到对同一个对象(vector)连续加锁、解锁操做,会合并一个更大范围的加锁、解锁操做,即加锁解锁操做会移到for循环以外。
轻量级锁
引入轻量级锁的主要目的是在多没有多线程竞争的前提下,减小传统的重量级锁使用操做系统互斥量产生的性能消耗。当关闭偏向锁功能或者多个线程竞争偏向锁致使偏向锁升级为轻量级锁,则会尝试获取轻量级锁,其步骤以下:
获取锁
1. 判断当前对象是否处于无锁状态(hashcode、0、01),如果,则JVM首先将在当前线程的栈帧中创建一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝(官方把这份拷贝加了一个Displaced前缀,即Displaced Mark Word);不然执行步骤(3);
2. JVM利用CAS操做尝试将对象的Mark Word更新为指向Lock Record的指正,若是成功表示竞争到锁,则将锁标志位变成00(表示此对象处于轻量级锁状态),执行同步操做;若是失败则执行步骤(3);
3. 判断当前对象的Mark Word是否指向当前线程的栈帧,若是是则表示当前线程已经持有当前对象的锁,则直接执行同步代码块;不然只能说明该锁对象已经被其余线程抢占了,这时轻量级锁须要膨胀为重量级锁,锁标志位变成10,后面等待的线程将会进入阻塞状态;
释放锁
轻量级锁的释放也是经过CAS操做来进行的,主要步骤以下:
1. 取出在获取轻量级锁保存在Displaced Mark Word中的数据;
2. 用CAS操做将取出的数据替换当前对象的Mark Word中,若是成功,则说明释放锁成功,不然执行(3);
3. 若是CAS操做替换失败,说明有其余线程尝试获取该锁,则须要在释放锁的同时须要唤醒被挂起的线程。
对于轻量级锁,其性能提高的依据是“对于绝大部分的锁,在整个生命周期内都是不会存在竞争的”,若是打破这个依据则除了互斥的开销外,还有额外的CAS操做,所以在有多线程竞争的状况下,轻量级锁比重量级锁更慢;
下图是轻量级锁的获取和释放过程
偏向锁
引入偏向锁主要目的是:为了在无多线程竞争的状况下尽可能减小没必要要的轻量级锁执行路径。上面提到了轻量级锁的加锁解锁操做是须要依赖屡次CAS原子指令的。那么偏向锁是如何来减小没必要要的CAS操做呢?咱们能够查看Mark work的结构就明白了。只须要检查是否为偏向锁、锁标识为以及ThreadID便可,处理流程以下:
获取锁
1. 检测Mark Word是否为可偏向状态,便是否为偏向锁1,锁标识位为01;
1. 若为可偏向状态,则测试线程ID是否为当前线程ID,若是是,则执行步骤(5),不然执行步骤(3);
1. 若是线程ID不为当前线程ID,则经过CAS操做竞争锁,竞争成功,则将Mark Word的线程ID替换为当前线程ID,不然执行线程(4);
4. 经过CAS竞争锁失败,证实当前存在多线程竞争状况,当到达全局安全点,得到偏向锁的线程被挂起,偏向锁升级为轻量级锁,而后被阻塞在安全点的线程继续往下执行同步代码块;
5. 执行同步代码块
释放锁
偏向锁的释放采用了一种只有竞争才会释放锁的机制,线程是不会主动去释放偏向锁,须要等待其余线程来竞争。偏向锁的撤销须要等待全局安全点(这个时间点是上没有正在执行的代码)。其步骤以下:
1. 暂停拥有偏向锁的线程,判断锁对象石是否还处于被锁定状态;
2. 撤销偏向苏,恢复到无锁状态(01)或者轻量级锁的状态;
下图是偏向锁的获取和释放流程
重量级锁
重量级锁经过对象内部的监视器(monitor)实现,其中monitor的本质是依赖于底层操做系统的Mutex Lock实现,操做系统实现线程之间的切换须要从用户态到内核态的切换,切换成本很是高。
参考资料 周志明:《深刻理解Java虚拟机》 方腾飞:《Java并发编程的艺术》 Java中synchronized的实现原理与应用)