Day292&293&294&295.Lock锁 -Juc
Posted 阿昌喜欢吃黄桃
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Day292&293&294&295.Lock锁 -Juc相关的知识,希望对你有一定的参考价值。
Lock锁
一、Lock接口
1、Lock简介&地位&作用
- 锁是一种工具,用于控制对
共享资源
的访问 - Lock并
不是用来替代
sychronized的,而是当使用sychronized不适合或不满足要求的时候,来提供高级功能
的。 - Lock接口最常见的实现类就是
ReentrantLock
2、为什么需要Lock?
为什么sychronized不够用
效率低
:
锁的释放情况少、试图获取锁时不能设定超时、不能中断一个正在试图获取锁的线程
不够灵活
:
加锁和释放的时机单一,每个锁仅有单一的条件(某个对象),可能是不够的
- 无法知道是否
成功获取到锁
3、Lock主要方法
lock()、tryLock()、tryLock(Long time,TimeUnit unit)和lockInterruptibly()
1)Lock
- lock()就是
获取锁
。如果锁已经被其他线程获取,则进行等待 - Lock不会像sychronized一样,
在发生异常时自动释放锁
- 因此最佳实践是,在
finally中释放锁
,以保证发生异常时锁一定被释放
/******
@author 阿昌
@create 2021-06-08 21:17
*******
* Lock不会像sychronized一样,在发生异常时,自动释放锁
*/
public class MustUnlock {
private static Lock lock = new ReentrantLock();
public static void main(String[] args) {
//加锁
lock.lock();
try {
//获取本锁保护的资源
System.out.println(Thread.currentThread().getName()+": 开始执行任务");
}finally {
//解锁
lock.unlock();
}
}
}
- lock()方法不能被中断,一旦陷入
死锁
,lock将陷入永久等待
2)tryLock
-
tryLock()用来
尝试获取锁
,如果当前线程没有被其他线程占用,则获取成功,则返回true,否则返回false,代表获取锁失败 -
相比上面的lock,他可以返回一个值,让我们知道是否成功获取到锁;进而
决定后续程序的行为
-
它会
立刻返回
,即便在拿不到锁时,不会一直等待
3)tryLock(long time,TimeUnit unit)
设置超时时间来设置放弃时间
- 通过tryLock避免死锁
在try/finally中的finally中设置unlock解锁,保证抢不到锁就释放锁
/******
@author 阿昌
@create 2021-06-08 21:26
*******
* 用tryLock避免死锁
*/
public class TryLockDeadLock implements Runnable {
int flag = 1;
static Lock lock1 = new ReentrantLock();
static Lock lock2 = new ReentrantLock();
public static void main(String[] args) {
TryLockDeadLock t1 = new TryLockDeadLock();
TryLockDeadLock t2 = new TryLockDeadLock();
t1.flag=1;
t2.flag=2;
new Thread(t1).start();
new Thread(t2).start();
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (flag == 1) {
try {
if (lock1.tryLock(800, TimeUnit.MILLISECONDS)) {
try {
System.out.println("线程1,获取lock1,成功");
Thread.sleep(new Random().nextInt(1000));
if (lock2.tryLock(800,TimeUnit.MILLISECONDS)){
try {
System.out.println("线程1,获取lock2,成功");
System.out.println("线程1,成功获取到了lock1、lock2");
break;
}finally {
lock2.unlock();
Thread.sleep(new Random().nextInt(1000));
}
}else {
System.out.println("线程1,获取lock2,失败");
}
} finally {
lock1.unlock();
Thread.sleep(new Random().nextInt(1000));
}
} else {
System.out.println("线程1,获取lock1,失败");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (flag == 2) {
try {
if (lock2.tryLock(800, TimeUnit.MILLISECONDS)) {
try {
System.out.println("线程2,获取lock2,成功");
Thread.sleep(new Random().nextInt(1000));
if (lock1.tryLock(3000,TimeUnit.MILLISECONDS)){
try {
System.out.println("线程2,获取lock1,成功");
System.out.println("线程2,成功获取到了lock1、lock2");
break;
}finally {
lock1.unlock();
Thread.sleep(new Random().nextInt(1000));
}
}else {
System.out.println("线程2,获取lock1,失败");
}
} finally {
lock2.unlock();
Thread.sleep(new Random().nextInt(1000));
}
} else {
System.out.println("线程2,获取lock2,失败");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
4)LockInterruptibly
public class LockInterruptibly implements Runnable{
private Lock lock = new ReentrantLock();
public static void main(String[] args) {
LockInterruptibly l = new LockInterruptibly();
Thread thread0 = new Thread(l);
Thread thread1 = new Thread(l);
thread0.start();
thread1.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread0.interrupt();
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+": 尝试获取锁");
try {
lock.lockInterruptibly();
try {
System.out.println(Thread.currentThread().getName()+": 拿到了锁");
Thread.sleep(5000);
}catch (InterruptedException e){
System.out.println("【睡眠期间】被中断");
} finally{
lock.unlock();
System.out.println(Thread.currentThread().getName()+": 释放锁");
}
} catch (InterruptedException e) {
System.out.println("【等锁期间】被中断");
e.printStackTrace();
}
}
}
4、锁的可见性保证
二、锁的分类
-
这些分类,是从
不同角度
出发去看的 -
这些分类并
不是互斥
的,也就是多个类型可以并存
:
有可能一个锁,同时属于两个类型
- 比如
ReentrantLock
既是互斥锁
,又是可重入锁
好比一个人可能同时是男人,又是军人
1、乐观锁&悲观锁
1)为什么会诞生乐观锁(非互斥同步锁)
-
悲观锁(互斥同步锁)的劣势
- 阻塞和唤醒带来
性能劣势
永久阻塞
:
如果持有的锁的线程被永久阻塞,比如遇到了无限循环、死锁等活跃性问题,那么等待该线程释放锁的那几个悲催的线程,将永远也得不到执行
优先级反转
若给线程设置了优先级,当一个比这个线程优先级低的线程拿到了这个悲观锁,如果他不释放,就需要一直等待,就算你优先级比他高,导致优先级错乱
- 阻塞和唤醒带来
2)什么是乐观锁&悲观锁
-
从
是否锁住资源
的角度分类 -
悲观锁:
- 乐观锁:
-
认为自己在处理操作的时候不会有其他线程来干扰,所有并
不会锁住
被操作对象 -
在更新的时候,去对比在我修改的期间数据有没有被其他人修改过:
如果
没有修改过
,就说明真的是只有我自己在操作,那就正常去修改数据 -
如果数据和我们
一开始拿到的不一样了
,说明其他人在这段那时间内修改过数据,那我们就不能继续刚才的更新操作,因此就会选择放弃、报错、重试等策略 -
乐观锁的实现一般都是利用
CAS算
(一个原子操作内)法来实现 -
例子:
- 原子类、并发容器
- Git版本管理器
- 数据库version字段
-
3)使用场景
悲观锁:
- 并发写入多的情况;
- 临界区持锁时间长的情况
- 避免大量无用自旋等消耗
乐观锁:
-
并发写入少;
-
大部分是读取场景,不加锁能让读取性能大幅提高
2、可重入&非可重入锁
以ReentrantLock
为例
1)使用案列
- 买电影院座位
//演示多线程预定电影院座位,一个座位不可能卖个2个人以上
public class CinemaBookSeat {
private static Lock lock = new ReentrantLock();
//预定座位
private static void bookSeat(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+": 【开始】预定座位");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+": 【完成】预定座位");
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
//主函数
public static void main(String[] args) {
new Thread(()->bookSeat()).start();
new Thread(()->bookSeat()).start();
new Thread(()->bookSeat()).start();
new Thread(()->bookSeat()).start();
}
}
- 打印字符串
public class LockDemo {
//主函数
public static void main(String[] args) {
LockDemo lockDemo = new LockDemo();
lockDemo.init();
}
//初始化
private void init(){
Outputer outputer = new Outputer();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
try {
Thread.sleep(5);
}catch (InterruptedException e){
e.printStackTrace();
}
outputer.print("阿昌");
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while (true){
try {
Thread.sleep(5);
}catch (InterruptedException e){
e.printStackTrace();
}
outputer.print("三月");
}
}
}).start();
}
static class Outputer{
Lock lock = new ReentrantLock();
//打印字符串
public void print(String name){
lock.lock();
try{
for (int i = 0; i < name.length(); i++) {
System.out.println(name.charAt(i));
}
System.out.println("");
}finally {
lock.unlock();
}
}
}
}
2)可重入性质
- 什么是可重入
如果已经拿到锁了,那我之后可再次进入到这个锁里面
如果一个锁里面有两个方法,如果他不是可重入锁,那他执行完第一个方法后,需要释放锁,再去获取锁,才能执行第二个方法
- 好处
- 避免死锁
- 提高封装性
3)代码演示
4)源码分析
可重入锁 & 非可重入锁的部分源码对比:↓↓↓
5)其他方法
- isHeldByCurrentThread:
可以看出锁是否被当前线程持有
- getQueueLength:
可以返回当前正在等待这个把锁的队列有多长
这两个方法是开发和调试的时候使用较多,上线后一般用不到
3、公平锁&非公锁
1)什么是公平和非公平
公平:指的是按照线程请求的顺序,来分配锁;
非公平:指的是不完全按照请求的顺序,在一定情况下,可插队。
非公平也同样不
提倡"插队"行为
,这里的非公平,指的是“在
合适的时机
”插队,而不是盲目插队
什么是适合时机?
一个人排队买票,现在在第二位,当他成为第一位的时候,他脑子懵了,傻了2秒;
此时之前在第一位的人,突然出现,问:XXX点的车几点发车,很快的。问完就走,此时你脑子懵。
这个列子体现,插队的时机,就算他没插队,那个人也不能第一时间去有效的利用这个时机,因为脑子懵的
2)为什么要有非公平锁
3)公平的情况(以ReentrantLock为例)
- 如果在创建ReentrantLock对象时,参数填写为true,那么这就是公平锁
4)不公平的情况(以ReentrantLock为例)
5)演示公平和非公平的效果
- 公平锁的演示
/******
@author 阿昌
@create 2021-06-09 22:26
*******
* 演示公平&非公怕情况
*/
public class FairLock {
public static void main(String[] args) {
PrintQueue printQueue = new PrintQueue();
Thread[] threadArr = new Thread[10];
for (int i = 0; i < threadArr.length; i++) {
threadArr[i] = new Thread(new Job(printQueue));
}
for (Thread thread : threadArr) {
thread.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace()以上是关于Day292&293&294&295.Lock锁 -Juc的主要内容,如果未能解决你的问题,请参考以下文章
转 : React Native 开发之 IDE 选型和配置
《安富莱嵌入式周报》第293期:SEGGER开源其C/C++库源码emRun,丰富EMC电磁兼容资,OTA开源组件,2022 Github全球报告,内存安全指南