“阅读本文大概需要8分钟。
你好,我是测试蔡坨坨。
在前几篇Redis相关文章中都说到了锁,同时我们在参加设计评审或者codeReview时也会接触到关于加锁的问题。因此,作为测试人员,还是很有必要搞懂相关的锁机制。
你是否背了很多关于锁的面试题,但还是没有搞懂锁到底有哪些东西,学了很多锁之后,发现越搞越模糊。
不要慌,本篇我们就来聊一聊Java中的各种锁。
说到锁,我们自然而然会想到Synchronized、Lock、Reentrantlock、分布式锁等很多锁的类型。
那么第一个问题,我们要搞清楚锁到底解决什么问题?
很简单,锁要解决的一个问题就是线程安全
问题。
所谓线程安全,主要体现在三方面:原子性、可见性和有序性。
而Synchronized同步关键字是可以解决我们在多线程开发领域中涉及到的线程安全问题。
线程安全问题在实际开发中又是如何体现的呢?
举个简单的栗子,有一个int类型的i=0存在主内存中,有两个线程Thread1和Thread2同时执行一个i++操作,此时这个结果可能等于1,也可能等于2。为什么呢?
因为i++这个指令是非原子指令,i++在Java中是一条指令,但是最终转成底层的汇编指令是三条指令:
两个线程同时操作这三条指令时,就有可能两个线程同时拿到i,结果就是i=2。
所以,我们在这种场景中需要加排他锁,也叫同步锁。
这里的同步锁起到什么作用呢?
在没有加锁之前可能出现最终结果等于2的情况,是因为两个线程同时执行,同时拿到i的值,也就是并行操作,而加上同步锁就是让并行变成串行。
同步锁的特点就是多个线程访问共享资源时,在同一时刻只允许一个线程访问这个共享资源,这样就能够解决原子性问题。
从功能层面来说,锁在Java并发编程中只有两类:共享锁
和排它锁
。
共享锁也叫读锁,读锁的特点是在同一时刻允许多个线程抢占到锁。
排它锁也叫写锁,写锁的特点是在同一时刻只允许一个线程抢占到锁。
我们经常听到的乐观/悲观锁、自旋锁、可重入锁、偏向锁、轻量/重量级锁又是什么呢?
这就需要从第二个维度进行拆分,加锁必然存在性能问题,因为加锁使得并行变成串行,并行的效率一定比串行高,加锁会造成阻塞。在软件开发中需要考虑两个点,性能和线程安全。例如:库存扣减,既要保证性能,又要保证线程的并发安全,保证原子性的修改。
在这个层面上来说,我们如何优化,如何权衡性能和线程安全两者之间的关系呢?
加锁的本质实际上是去竞争一个同步状态,如上图所示,有一个文件,两个线程需要去竞争文件的访问资格,如何知道是否有访问资格呢?可以通过一个同步标识,比如int status=0/1(0表示空闲,1表示繁忙),线程1竞争到锁,进入访问时将status修改成1,线程2再进入到锁判断时,只需要去判断当前的status是否等于1即可。
Synchronized是通过操作系统层面的Mutex机制(mutually exclusive)实现同步状态,通过竞争Mutex机制,实现互斥状态的处理。线程1和2去竞争Mutex的时候,会涉及到内核指令的调用,因为Mutex是操作系统层面提供的一个互斥机制,所以需要通过内核指令去调用这个机制来实现互斥竞争行为。
这个地方就会涉及到用户态
和内核态
的切换,这个切换会占用CPU资源,消耗性能,因为用户线程要进入阻塞等待,然后切换到内核线程来运行,需要把当前运行的线程执行指令的上下文保存起来,同时要切换到内核线程去执行指令,也就是会涉及到线程的阻塞
、唤醒
以及上下文的保存
。
假设线程2竞争到了锁,线程1就会进入阻塞等待,所以加锁会影响性能。
性能主要体现在三个方面:
1.竞争同步状态时涉及到上下文切换,也就是从用户态到内核态的切换
2.线程阻塞和唤醒的切换
3.并行到串行的改变
由于1和3无法改变,所以我们重点关注第二点线程的阻塞和唤醒,这个地方的切换是否能够避免,也就是说线程2竞争到锁之后,线程1不去阻塞等待,也就是让线程1在阻塞之前进行重试(重试就是线程1第一次尝试加锁,发现线程2已经获取到锁,这时就进入下一次循环再进行重试)。这种方式也就是所谓的自旋锁
,在阻塞等待之前通过一定的自旋尝试去竞争锁资源,也叫做轻量级锁
。
咱就是说我们加锁的代码有没有可能压根就不存在竞争场景?有可能。
我们加锁的目的是保证这段代码的线程安全性,但是有可能在实际开发中这段代码压根就不存在竞争。
举个栗子,前端页面做了非空校验,理论上传给后端的参数就不会为空,但是也有可能有人直接调用接口传一个空值,所以后端一般都做非空校验,也叫做防御性编程。
同理,如果一段代码中锁的竞争必要性不存在
,但是我们又想保护这段代码,于是就引入了偏向锁。
所谓偏向锁就是当线程1进入锁的时候,如果当前不存在竞争,那么它就会把这个锁偏向线程1,线程1下次再进入的时候,就不再需要竞争锁。
简而言之,偏向锁可以认为没有竞争,轻量级锁存在轻微竞争,而重量级锁就是整个的实现。
第三维度从锁的特性来说,又会有重入锁和分布式锁。
以上,完。
脚踏实地,仰望星空,和坨坨一起学习软件测试,升职加薪!