本篇文章主要是记录自己的学习笔记,主要内容是:公平锁、非公平锁、可重入锁、递归锁、自旋锁的理解,并实现一个自旋锁。
一、公平和非公平锁
(1)公平锁和非公平锁是什么?
公平锁:是指多个线程按照申请锁的顺序来获取锁,类似排队打饭,先来后到。
非公平锁:是指多个线程获取锁的顺序并不是按照申请锁的循序,有可能后申请的线程比先申请的线程优先获取锁。但是,在高并发的情况下,有可能会造成优先级反转或者饥饿现象。
(2)公平锁和非公平锁的区别是什么?
并发包中ReentrantLock的创建可以指定构造函数的boolean类型来得到公平锁。
公平锁和非公平锁两者的区别:
公平锁:线程按照他们申请锁的顺序获取锁,公平锁就是很公平,在并发环境下,每个线程在获取锁时会先查看此锁维护的等待队列,如果队列为空,获取当前线程时等待队列的第一个,就占有锁。否则,就会加入到等待队列中,以后会按照FIFO的规则从队列中获取锁。
非公平锁:非公平所比较粗鲁,上来就尝试占有锁,如果尝试失败,就采用类似公平锁的方式获取锁。
补充:ReentrantLock即使通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点就是吞吐量比公平锁大。同样的synchronized也是一种非公平锁。
二、可重入锁(又名递归锁)
可重入锁定义:指的是同一线程外层函数获得锁忠厚,内层递归函数仍能获取该锁。在同一个线程在外层党法获取锁的时候,在进入内层方法会自动获取锁。也就是说。线程可以进入任何一个它已经拥有的锁所同步着的代码块。
ReetrantLock和synchronized就是一个典型的可重入锁,其最大的作用就是避免死锁。
下面我们用代码来进行验证:
class Phone{
public synchronized void sendMessage(){
System.out.println(Thread.currentThread().getId() + " \t " + " invoked sendMessage");
sendEmail();
}
public synchronized void sendEmail(){
System.out.println(Thread.currentThread().getId() + " \t " + " ###### invoked sendEmail");
System.out.println("----------------------------------- 分割线 -----------------------------------");
System.out.println();
}
}
public class ReetrantLockDemo {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sendMessage();
}).start();
new Thread(()->{
phone.sendMessage();
}).start();
}
}
输出结果:
对于RetrantLock同样可得上述结果:
class Phone{
Lock lock = new ReentrantLock();
public void sendMessage(){
try {
lock.lock();
System.out.println(Thread.currentThread().getId() + " \t " + " invoked sendMessage");
sendEmail();
}finally {
lock.unlock();
}
}
public synchronized void sendEmail(){
try {
lock.lock();
System.out.println(Thread.currentThread().getId() + " \t " + " ###### invoked sendEmail");
System.out.println("----------------------------------- 分割线 -----------------------------------");
System.out.println();
}finally {
lock.unlock();
}
}
}
public class ReetrantLockDemo {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(()->{
phone.sendMessage();
}).start();
new Thread(()->{
phone.sendMessage();
}).start();
}
}
输出结果同上。
三、自旋锁(spinlock)
自旋锁:是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是:循环会消耗CPU资源。
我们学习过的CAS原理,就是采用了自旋锁的思想:
了解了自旋锁的原理之后,我们自己实现一个自旋锁。
public class SpinLockDemo {
AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void myLock(){
System.out.println(Thread.currentThread().getName() + "\t come in");
Thread thread = Thread.currentThread();
while (!atomicReference.compareAndSet(null, thread)){
}
}
public void myUnLock(){
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread, null);
System.out.println(Thread.currentThread().getName() + "\t leave out");
}
public static void main(String[] args) {
SpinLockDemo demo = new SpinLockDemo();
new Thread(()->{
demo.myLock();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
demo.myUnLock();
}, "AAA").start();
new Thread(()->{
demo.myLock();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
demo.myUnLock();
}, "BBB").start();
}
}
输出结果:
四、独占锁(写锁)/共享锁(读锁)/互斥锁
独占锁:指该锁一次只能被一个线程所持有。对ReentrantLock和synchronized来说,它们都是独占锁。
共享锁:指该锁可以被多个线程所持有。
对ReentrantReadWriteLock来说,读锁是共享锁,写锁是独占锁。
读锁的共享锁可以保证并发读是非常高效的,读写,写读,写写的过程是互斥的。
也就是说,多个线程同时读取一个资源类没有任何问题,所以为了满足并发量,读取共享资源应该可以同时进行。但是,如果有一个线程想去写共享资源,就不应该又其他的线程对资源进行读或者写。
总结:读-读可共存
读-写不可以共存
写-写不可以共存
代码实现小例子:
class CacheResource{
private volatile Map<String, Object> map = new HashMap<>();
private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
public void put(String key, Object value){
try {
reentrantReadWriteLock.writeLock().lock();
System.out.println("--------------------------------------------------------------");
System.out.println(Thread.currentThread().getId() + " \t " + "正在写入" + key);
}finally {
System.out.println(Thread.currentThread().getId() + " \t " + "写入成功" + key);
System.out.println("--------------------------------------------------------------");
reentrantReadWriteLock.writeLock().unlock();
}
}
public void get(String key){
try {
reentrantReadWriteLock.readLock().lock();
System.out.println(Thread.currentThread().getId() + " \t " + "正在读取 \t" + key);
}finally {
System.out.println(Thread.currentThread().getId() + " \t " + "读取成功 \t" + key);
reentrantReadWriteLock.readLock().unlock();
}
}
}
public class ReentrantReadWriteLockDemo {
public static void main(String[] args) {
CacheResource resource = new CacheResource();
for (int i = 0; i < 5; i++){
final int tmp = i;
new Thread(()->{
resource.put(tmp + "", "");
}).start();
}
for (int i = 0; i < 5; i++){
final int tmp = i;
new Thread(()->{
resource.get(tmp + "");
}).start();
}
}
}
输出结果:
12 正在写入1
12 写入成功1
11 正在写入0
11 写入成功0
13 正在写入2
13 写入成功2
14 正在写入3
14 写入成功3
15 正在写入4
15 写入成功4
16 正在读取 0
16 读取成功 0
18 正在读取 2
18 读取成功 2
19 正在读取 3
19 读取成功 3
20 正在读取 4
20 读取成功 4
17 正在读取 1
17 读取成功 1
Process finished with exit code 0