本篇文章主要是记录自己的学习笔记,主要内容是:公平锁、非公平锁、可重入锁、递归锁、自旋锁的理解,并实现一个自旋锁。
一、公平和非公平锁
(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 { AtomicReferenceatomicReference = 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 Mapmap = 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
到这里,本篇文章就结束了,虽然写博客会花费一定的时间,但是可以加深自己对知识点的理解,便于日后复习,要坚持。