今天,我们从 Java 内部锁优化,代码中的锁优化,以及线程池优化几个方面展开讨论。
Java 内部锁优化
当使用 Java 多线程访问共享资源的时候,会出现竞态的现象。即随着时间的变化,多线程“写”共享资源的最终结果会有所不同。
为了解决这个问题,让多线程“写”资源的时候有先后顺序,引入了锁的概念。每次一个线程只能持有一个锁进行写操作,其他的线程等待该线程释放锁以后才能进行后续操作。
从这个角度来看,锁的使用在 Java 多线程编程中是相当重要的,那么是如何对锁进行优化?
—————————————————————————————————————
如果大家对于学习Java有任何问题(学习方法,学习效率,如何就业),可以随时来咨询我,这是我的Java交流学习扣扣群:六三零,四七三,七一 一。 多多交流问题,互帮互助,群里有不错的学习教程和开发工具。
众所周知,Java 的锁分为两种:
一种是内部锁,它用 Synchronized 关键字来修饰,由 JVM 负责管理,并且不会出现锁泄漏的情况。
另外一种是显示锁。
这里重点讨论的是内部锁优化。内部锁的优化方式由 Java 内部机制完成,虽然不需要程序员直接参与,但了解它对理解多线程优化原理有很大帮助。
这部分的优化主要包括四部分:
锁消除
锁粗化
偏向锁
适应锁
锁消除(Lock Elision),JIT 编译器对内部锁的优化。在介绍其原理之前先说说,逃逸和逃逸分析。
逃逸是指在方法之内创建的对象,除了在方法体之内被引用之外,还在方法体之外被其他变量引用。
也就是,在方法体之外引用方法内的对象。在方法执行完毕之后,方法中创建的对象应该被 GC 回收,但由于该对象被其他变量引用,导致 GC 无法回收。
这个无法回收的对象称为“逃逸”对象。Java 中的逃逸分析,就是对这种对象的分析。
回到锁消除,Java JIT 会通过逃逸分析的方式,去分析加锁的代码段/共享资源,他们是否被一个或者多个线程使用,或者等待被使用。
如果通过分析证实,只被一个线程访问,在编译这个代码段的时候就不生成 Synchronized 关键字,仅仅生成代码对应的机器码。
换句话说,即便开发人员对代码段/共享资源加上了 Synchronized(锁),只要 JIT 发现这个代码段/共享资源只被一个线程访问,也会把这个 Synchronized(锁)去掉。从而避免竞态,提高访问资源的效率。
锁消除示意图
作为开发人员来说,只需要在代码层面去考虑是否用 Synchronized(锁)。
说白了,就是感觉这段代码有可能出现竞态,那么就使用 Synchronized(锁),至于这个锁是否真的会使用,则由 Java JIT 编译器来决定。
锁粗化(Lock Coarsening) ,是 JIT 编译器对内部锁具体实现的优化。假设有几个在程序上相邻的同步块(代码段/共享资源)上,每个同步块使用的是同一个锁实例。
那么 JIT 会在编译的时候将这些同步块合并成一个大同步块,并且使用同一个锁实例。这样避免一个线程反复申请/释放锁。
锁粗化示意图
如上图存在三块代码段,分割成三个临界区,JIT 会将其合并为一个临界区,用一个锁对其进行访问控制。
即使在临界区的空隙中,有其他的线程可以获