目录
一、悲观锁VS乐观锁
1.悲观锁
2.乐观锁
二、重量级锁VS轻量级锁
1.重量级锁
2.轻量级锁
三、自旋锁
1.自旋锁概念
四、公平锁VS非公平锁
1.公平锁
2.非公平锁
3.注意
五、可重入锁和不可重入锁
六、读写锁
1.线程对于数据的访问方式
注意:以下讲解的锁策略不止局限于Java,任何和“锁”有关的话题都会牵扯到以下的内容,这些特性主要是给锁的设计者来参考的。
普通的程序员也需要了解一下,这对合理的使用锁有很大的帮助。
一、悲观锁VS乐观锁
1.悲观锁
总是假设最坏的情况,也就是每次拿到数据都会认为这个数据会被别人拿到修改,所以在每次拿到数据都会加上锁,这样别人想要获取数据就会阻塞直到它拿到锁。
2.乐观锁
假设数据一般情况下不会产生并发冲突,所以在数据提交更新的时候才会对数据是否会产生并发冲突进行判断,如果发现并发冲突就会给返回一个用户错误信息,让用户来决定怎么做。
举例来说:同学A和同学B向老师请教问题。
同学A认为老师是很忙的,去问问题老师大概率在忙不能帮他解决问题,所以他先给老师发信息问“老师您有空吗?我可以问个问题吗?”(相当于加锁操作)得到肯定的答复后,他才会真的去向老师问问题,得到否定的答复后,他会再等一段时间等到老师空闲的时候问问题,这个就是悲观锁。
同学B认为老师是比较闲的,去问问题老师大概率是有时间解答的,所以他直接去找老师(没加锁,直接访问资源)如果老师比较闲,问题直接就解决了,如果老师正好在忙,那么他也不会打扰老师,下次再来(虽然没加锁,但是能识别出数据访问冲突),这个就是乐观锁。
这两种思路并不能说谁优谁劣,要看当前场景适合哪种锁。
我们Java的Synchronized初始情况下是乐观锁,在运行过程中发现锁竞争频繁后会自动转为悲观锁。
二、重量级锁VS轻量级锁
首先我们要明白锁的核心特性“原子性”这样的机制追根溯源可以追溯到CPU这样的硬件设备提供。
CPU提供了“原子性操作”。
操作系统通过CPU的原子指令实现了mutex互斥锁
JVM基于操作系统提供的互斥锁,实现了Synchronized 和ReentrantLock等关键字和子类。
1.重量级锁
加锁机制重度依赖OS提供的mutex。
大量的内核态用户态切换;很容易引发线程调度。
这两个操作的成本都很高,一旦引发了内核态和用户态的切换就意味着“沧海桑田”。
2.轻量级锁
加锁机制尽可能不使用OS提供的mutex,尽量在用户态代码完成,实在不行了在使用mutex。
少量的内核态用户态切换;不容易引发线程调度。
synchronized 开始是⼀个轻量级锁. 如果锁冲突⽐较严重, 就会变成重量级锁
三、自旋锁
按之前的方式,线程在抢锁失败后就会进入阻塞状态,放弃CPU,下次再次调用该线程就不知道是什么时候了。实际上,在线程抢锁失败后用不了多长时间,锁就会被释放,没必要放弃CPU,这时我们就可以使用自旋锁来解决这样的问题。
1.自旋锁概念
综上所述,在一个线程抢锁失败后,它会立即再次尝试获取锁,无限循环,这样在该锁释放的时候它能够立刻获取到锁。
自旋锁是一种典型的轻量级锁的实现方式:
优点:没有放弃CPU,不涉及线程的调度和阻塞,能够第一时间获取到锁。
缺点:如果锁被其他线程获取的时间比较长,就会持续不断的消耗CPU资源(挂起等待是不需要消耗CPU资源的)
Synchronized中的轻量锁策略大概率就是通过自旋锁的方式实现的。
四、公平锁VS非公平锁
假设有ABC三个线程,A线程先获取到锁,B线程尝试获取锁失败阻塞等待,C线程再尝试获取锁失败阻塞等待,那么当A线程释放锁后哪个线程获取到锁呢?
1.公平锁
遵守先来后到的规则,B线程比C线程更早尝试获取锁,所以B线程获取到锁。
2.非公平锁
不遵守先来后到的规则,BC两个线程都有可能获取到锁。
这就好比一群男生追一个女生,当女生和前任分手后,她挑选了追她最久的做男朋友这就是“公平锁”,如果她随机挑了一个她看着顺眼的这就是“非公平锁”。
3.注意
操作系统中线程的调度时随机的,如果不做任何限制的话,那么锁就是非公平锁,如果要实现公平锁需要用额外的数据结构来记录每个线程尝试获取锁的时间来实现公平锁。
公平锁和非公平锁直接没用优劣之分,具体看使用场景。
Synchronized实现的是非公平锁。
五、可重入锁和不可重入锁
可重入锁可以根据字面意思来理解,就是可以重复进入的锁,也就是说一个线程可以重复获取统一把锁,
六、读写锁
多线程之间,数据的读取方之间不需要进行互斥,数据的写入方之间和写入方和读取方之间需要进行互斥。如果这几种场景下使用同一种锁就会产生极大的性能消耗,因此有了读写锁的产生。
1.线程对于数据的访问方式
一个线程对于数据的访问方式无非两种,读数据和写数据:
两个线程都是读一个数据,此时没有线程安全问题,并发的读取即可;
一个线程读数据一个线程写数据,有线程安全问题;
两个线程都是写数据,也有线程安全问题。
读写锁就是把读和写区别对待,