Java的无锁编程和锁优化

时间:2021-10-17 12:15:50

这篇文章里面已经提到了三件事情:1、Peterson 算法,2、ConcurrentHashMap,3、无锁编程的初识。

 

如果站在语言层面之上,仅从设计的层面看,可以避免锁的思路至少包括:

1、单线程来主导行为,多线程池化操作避开状态变量。

比如在一个WEB应用中,每一个Action都可以给相应的用户线程分配一个实例,线程之间互不干扰;但是到了业务逻辑Service内,避开Service状态变量的使用,减少了开发人员对并发编程的关注。

2、函数式编码。

函数式编码是最天然的和最高效的免锁方式,如果你对函数式编码还不了解,请参看这篇文章

3、资源局部复制、异步处理。

总所周知对资源的争夺是造成锁的一个重要原因,在很多情况下,资源只能有一份,但是对使用资源的每个线程来说,都可以看到属于它自己的一份(这一份并非是真正的资源,很可能只是一个缓冲区,每个线程使用它自己的一个缓冲区,到一定程度时将缓冲区的数据处理到唯一资源中,这就减少了需要加锁对线程的影响),无需考虑并发地去使用。

 

Java的锁操作和锁优化:

锁自旋

线程要进入阻塞状态,肯定需要调用操作系统的函数来完成从用户态进入内核态的过程,这一步通常是性能低下的。

那么在遇到锁的争用时,或许等待线程可以不那么着急进入阻塞状态,而是等一等,看看锁是不是马上就释放了,这就是锁自旋。锁自旋在多处理器上有重要价值。

当然锁自旋也带来了一些问题,比如如何判断自旋周期,如何确定自旋锁的个数,如何处理线程优先级差异等。

 

锁偏向

锁偏向是JDK1.6引入的,主要为了解决在没有竞争情况下锁的性能问题。

锁都是可重入的,在已经获得锁的情况下,该线程可以多次锁住该对象,但是每次执行这样的操作会因为CAS(CPU的Compare-And-Swap指令)操作而造成一些开销,为了减少这种开销,这个锁会偏向于第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他的线程获取,则持有偏向锁的线程将永远不需要再进行同步。

 

锁消除

(JDK1.6)锁削除是指虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行削除。

 

锁膨胀

(JDK1.6)和数据库中的锁升级有些相似,多个或多次调用粒度太小的锁,进行加锁解锁的消耗,反而还不如一次大粒度的锁调用来得高效,因此JVM可将锁的范围优化到更大的区域。

 

轻量级锁

(JDK1.6)轻量级锁能提升程序同步性能的依据是“对于绝大部分的锁,在整个同步周期内都是不存在竞争的”,这是一个经验数据。轻量级锁在当前线程的栈帧中建立一个名为锁记录的空间,用于存储锁对象目前的指向和状态。如果没有竞争,轻量级锁使用CAS操作避免了使用互斥量的开销,但如果存在锁竞争,除了互斥量的开销外,还额外发生了CAS操作,因此在有竞争的情况下,轻量级锁会比传统的重量级锁更慢。