一、常见的锁策略
1、悲观锁&&乐观锁
悲观锁:预期锁冲突的概率很高,做的工作更多,付出的成本更多,更低效
乐观锁:预期锁冲突的概率很低,做的工作更少,付出的成本更低,更高效
2、读写锁&&普通的互斥锁
普通的互斥锁只有两个操作:加锁和解锁
读写锁有三个操作:加读锁(如果代码中只是进行读操作,就加读锁)、加写锁(如果代码中进行了修改操作,就加写锁)、解锁
针对读锁和读锁之间是不存在互斥关系的(因为多线程同时读同一个变量不会有线程安全问题),读锁和写锁之间,写锁和写锁之间才需要互斥
3、重量级锁&&轻量级锁
重量级锁:就是做了更多的事,开销比较大,通常情况下悲观锁一般都是重量级锁
轻量级锁:就是做了更少的事,开销比较小,通常情况下乐观锁一般都是轻量级锁
在使用锁的过程中,如果锁是基于内核的一些功能来实现的(比如调用了操作系统提供的mutex接口)此时一般认为这是重量级锁(操作系统会在内核中做很多事情,比如让线程阻塞等待)
如果锁是纯用户态来实现的,此时一般认为这是轻量级锁(用户态的代码更可控,也更高效)
4、挂起等待锁&&自旋锁
挂起等待锁:往往是通过内核的一些机制来实现的,往往较重 【重量级锁的一种典型实现】
自旋锁:往往就是通过用户态代码来实现的,往往较轻 【轻量级锁的一种典型实现】
5、公平锁&&非公平锁
公平锁:多个线程在等待一把锁的时候,谁是先来的,谁就能先获取到这个锁(遵循先来后到)
非公平锁:多个线程在等待一把锁的时候,不遵守先来后到(每个线程获取到锁的概率都是均等的)
6、可重入锁&&不可重入锁
一个线程针对一把锁,咔咔连续加锁多次,如果会死锁,就是不可重入锁,如果不会死锁,就是可重入锁
相关面试题:
1) 你是怎么理解乐观锁和悲观锁的,具体怎么实现呢?悲观锁认为多个线程访问同一个共享变量冲突的概率较大 , 会在每次访问共享变量之前都去真正加锁.乐观锁认为多个线程访问同一个共享变量冲突的概率不大 . 并不会真的加锁 , 而是直接尝试访问数据. 在访问的同时识别当前的数据是否出现访问冲突 .悲观锁的实现就是先加锁 ( 比如借助操作系统提供的 mutex), 获取到锁再操作数据 . 获取不到锁就等待.乐观锁的实现可以引入一个版本号 . 借助版本号识别出当前的数据访问是否冲突 . ( 实现细节参考上面的图).2) 介绍下读写锁?读写锁就是把读操作和写操作分别进行加锁 .读锁和读锁之间不互斥 .写锁和写锁之间互斥 .写锁和读锁之间互斥 .读写锁最主要用在 " 频繁读 , 不频繁写 " 的场景中 .3) 什么是自旋锁,为什么要使用自旋锁策略呢,缺点是什么?如果获取锁失败 , 立即再尝试获取锁 , 无限循环 , 直到获取到锁为止 . 第一次获取锁失败 , 第二次的尝试会在极短的时间内到来. 一旦锁被其他线程释放获取到锁相比于挂起等待锁 ,优点 : 没有放弃 CPU 资源 , 一旦锁被释放就能第一时间获取到锁 , 更高效 . 在锁持有时间比较短的场景下非常有用.缺点 : 如果锁的持有时间较长 , 就会浪费CPU资源4) synchronized 是可重入锁么?是可重入锁 .可重入锁指的就是连续两次加锁不会导致死锁 .实现的方式是在锁中记录该锁持有的线程身份 , 以及一个计数器 ( 记录加锁次数 ). 如果发现当前加锁的线程就是持有锁的线程, 则直接计数自增 .
二、CAS——compare and swap比较和交换
拿着寄存器/某个内存中的值和另一个内存的值进行比较,如果值相同,就把另一个寄存器/内存的值和当前的这个内存进行交换
假设内存中的原数据V,旧的预期值A,需要修改的值B
1、比较A与V是否相等(比较)
2、如果比较相等,将B写入V(交换)
3、返回操作是否成功
CAS指的是CPU提供了一个单独的CAS指令,通过这一条指令(原子的),就能完成上述过程
由于CAS指令是原子的,可以用来写多线程的代码
CAS如何能解决线程
本文深入探讨了多线程中的锁策略,包括悲观锁与乐观锁、读写锁与互斥锁、重量级锁与轻量级锁、挂起等待锁与自旋锁、公平锁与非公平锁以及可重入锁。此外,文章还讲解了CAS操作在解决线程安全问题上的应用,以及synchronized的优化机制,如锁膨胀和锁消除。最后,介绍了Java并发工具类,如Callable、ReentrantLock、Semaphore和CountDownLatch,以及线程安全的集合类在多线程环境下的使用。
最低0.47元/天 解锁文章
1806

被折叠的 条评论
为什么被折叠?



