首页 > 编程语言 >并发编程之锁

并发编程之锁

时间:2022-11-03 00:22:06浏览次数:66  
标签:之锁 获取 写锁 Lock 编程 并发 读锁 线程 tryLock

什么是Lock

  • 锁是一种工具,用于控制对共享资源的访问。
  • Lock 和 synchronized 作用相同,都可以实现线程安全的目的。
  • Lock 不会像 synchronized 一样在异常时自动释放锁。
  • Lock 锁的是锁对象本身

synchronized 的不足之处

  1. 效率低:锁的释放情况少、试图获得锁时不能设定超时、不能中断一个正在试图获得锁的线程
  2. 不够灵活(读写锁更灵活):加锁和释放的时机单一,每个锁仅有单一的条件(某个对象),可能是不够的
  3. 无法知道是否成功获取到锁

lock() 、 tryLock() 、 tryLock(long time, TimeUnit unit) 和 lockInterruptibly()

  • lock() 方法不能被中断,这会带来很大的隐患:一旦陷入死锁,lock() 就会陷入永久等待。
  • tryLock() 用来尝试获取锁,如果当前锁没有被其他线程占用,便会获取成功,则返回 true,否则返回 false,代表获取锁失败(该方法会立即返回,即便在拿不到锁时不会一直在那等待获取锁)
  • tryLock(long time, TimeUnit unit):超时就放弃获取锁
  • lockInterruptibly():相当于 tryLock(long time, TimeUnit unit) 把超时时间设置为无限。在等待锁的过程中,线程可以被中断。

lockInterruptibly()和tryLock(time, unit)都拥有接收中断信号的能力,只是lockInterruptibly()是特殊的tryLock(time, unit),它的超时时间是无限大的,或者说它是具有被中断能力的tryLock();

注意点:不论是 Lock 还是 synchronized ,两者的可见性要求,如果下一个线程没有尝试获取锁,那么该线程是不能够看到前一个线程释放锁之前的操作的,也就是说下一个线程想要看到前一个线程释放锁之前的操作时,就必须先要获取这把锁,否则下一个线程得到的数据可能不是最新的。

乐观锁和悲观锁

乐观锁也成为非互斥同步锁,悲观锁也称为互斥同步锁。

互斥同步锁的劣势:

  • 堵塞和唤醒带来的性能劣势。悲观锁,当它锁住之后,便是独占的,其他线程想要获取该资源,就必须等待。
  • 永久堵塞:如果持有锁的线程被永久堵塞,比如遇到了无限循环、死锁等活跃性问题,那么等待该线程释放锁的那几个线程将无法得到执行。

两者的区别在于是否需要先拿到锁,再去操作。

悲观锁:悲观锁为了结果的正确性,会在每次获取并修改数据时,把数据锁住,让别人无法访问该数据,这样可以确保数据内容万无一失。synchronized 和 Lock 相关类就是悲观锁的实现。

乐观锁:认为自己在处理操作的时候不会有其他线程来干扰,所以不会锁住被操作对象。如果数据和一开始拿到的数据不一样,说明有其他线程在这段时间内改过数据,则直接放弃、报错、重试等策略。乐观锁的实现一般都是利用 CAS 算法来实现的。典型例子是原子类并发容器等。

  • Git 就是乐观锁的典型例子,当我们往远端仓库 push 的时候,git 会检查远端仓库的版本是不是领先于我们现在的版本,如果远程仓库的版本号和本地的不一样,就表示有其他人修改了远端代码了,就无法成功提交;如果远端和本地版本号一致,就可以顺利提交版本到远程仓库。

开销对比:

  • 悲观锁的原始开销要高于乐观锁,但是特点是一劳永逸,临界区(每个进程中,访问临界资源的那段代码,临界资源:一次仅允许一个进程使用的资源)持锁时间就算越来越差,也不会对互斥锁的开销造成影响,因为开销是固定的。所以比较适合于并发写入多的场景。
  • 乐观锁刚开始的开销比悲观锁小,但是如果自旋的时间过长或者不停重试,那么消耗的资源也会越来越多。适合并发写入少,大部分是读取的场景。

可重入锁和非可重入锁(ReentrantLock)

什么是可重入?

再次去申请这个锁的时候,无需提前释放掉我这把锁,而是可以直接继续使用这把锁。

好处是避免死锁

公平锁和非公平锁

公平指的是按照线程请求的顺序,来分配锁;非公平指的是,不完全按照请求的顺序,在一定情况下,可以插队。

设计的初衷:避免线程从堵塞到唤醒期间带来的空档期,提高系统的吞吐量。

ReentrantLock 默认就是不公平锁。

  • 针对tryLock()方法,它是不遵守设定的公平的规则,即非公平
  • 例如,当有线程执行tryLock()的时候,一旦有线程释放了锁,那么这个正在tryLock的线程就能获取到锁,即使在它之前已经有其他的线程在等待队列里了。

优势 劣势
公平锁 各线程公平平等,每个线程在等待一段时间后,总有执行的机会 更慢,吞吐量小
非公平锁 更快,吞吐量大 有可能产生线程饥饿,也就是某些线程在长时间内,始终得不到执行
  1. 非公平锁的情况下,唤醒线程是按照队列顺序依次唤醒的还是同时唤醒一起抢锁? 按照队列顺序依次唤醒
  2. 如果是依次唤醒的,排在第一位的线程每次都抢不过新来的线程是不是它下次还在第一位还是首先唤醒它? 是的
  3. 在等待队列里面的是依次唤醒的,没在等待队列的是可以抢锁的是吧

共享锁和排它锁(ReentrantReadWriteLock)

  • 排他锁,又称为独占锁、独享锁
  • 共享锁,又称为读锁,获得共享锁之后,可以查看但无法修改和删除数据,其他线程此时也可以获取到共享锁,也可以查看但无法修改和删除数据
  • 共享锁和排它锁的典型是读写锁 ReentrantReadWriteLock ,其他读锁是共享锁,写锁是独享锁。

读写锁的规则

  1. 多个线程只申请读锁,都可以申请到
  2. 如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁。
  3. 如果有一个线程已经占用了写锁,则此时其他线程如果要申请写锁或者读锁,则申请写锁的线程会一直等待释放写锁。
  4. 要么是一个或多个线程同时拥有读锁,要么是一个线程拥有写锁。

加读锁的作用就是为了保证并发安全,虽然看上去不加锁也可以读取,但是如果我们加了锁之后,就可以防止读写同时发生的情况发生,一旦我们在读取该值之前率先加锁,那么如果有其他线程想在我们读取期间修改该值的话,他是无法做到的,因为他无法在我们读取期间获取写锁。

自旋锁和阻塞锁

自旋锁:当前线程在不放弃CPU的情况下,不断地去尝试获取锁,当持有锁的线程释放掉锁之后,这样当前线程就不会陷入堵塞而是直接获取资源,从而避免线程切换的开销。适合于并发度不是特别高的情况下。

堵塞锁:如果当前线程没有拿到锁,就直接陷入堵塞状态。

可中断锁(可以响应的锁)

synchronized 就不是可中断锁,而Lock是可中断锁。因为在tryLock(time) 和 lockInterruptibly 都能响应中断。

即当某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,那么在这等待期间线程B是可以被中断的。

锁的优化

  1. 缩小同步代码块
  2. 减少请求锁的次数。(可以把多个请求封装成一个去统一请求)
  3. 锁中尽量不要再包含锁。(容易造成死锁)
  4. 选择合适的锁类型或者锁工具类。

标签:之锁,获取,写锁,Lock,编程,并发,读锁,线程,tryLock
From: https://www.cnblogs.com/hyxiao97/p/16853047.html

相关文章

  • Java函数式编程:二、高阶函数,闭包,函数组合以及柯里化
    承接上文:Java函数式编程:一、函数式接口,lambda表达式和方法引用这次来聊聊函数式编程中其他的几个比较重要的概念和技术,从而使得我们能更深刻的掌握Java中的函数式编程。......
  • java----GUI编程
    1packagecom.cilinmengye.HouseWork5;23importjavax.swing.*;4importjava.awt.*;5importjava.awt.event.ActionEvent;6importjava.awt.event.Actio......
  • Java GUI编程(未完待续...)
    JavaGUIAWT1.组件和容器Frame弹出一个窗口packageuichuan;importjava.awt.*;importjava.awt.event.WindowAdapter;importjava.awt.event.WindowEvent;pu......
  • 《Unix/Linux系统编程》第十二章学习笔记
    第十二章  块设备I/O和缓冲区管理12.1块设备I/O缓冲区I/O缓冲的基本原理非常简单。文件系统使用一系列I/O缓冲区作为块设备的缓存内存。当进程试图读取(dev,blk)标识......
  • Python基础之面向对象:1、面向对象及编程思想
    一、人狗大战1、需求用代码模拟人、狗打架的小游戏人和狗种类不同,因此双方的属性各不相同推导一:人和狗各有不同属性使用字典方式储存属性较为方便,并可储存多......
  • Android Studio编程第一篇:反应时间测试(RTI)
    目标参与者必须选择并按住屏幕底部的一个按钮。上面有一个圆圈(一个用于简单模式,五个用于五种选择模式)。在每一种情况下,其中一个圆圈中都会出现一个黄色的圆点,参与者必须尽......
  • Android Studio编程遇到的问题和常用模式总结
    起源一个学精神医科的朋友写论文需要做交互性的实验,让我帮忙做一套APP,主要用于测试病人反应速度,需要在移动端上实现,python-for-android部署起来很折腾,做成网页版的话还需......
  • 第四届全国大学生算法设计与编程挑战赛(秋季赛)T5.找规律
    看了题解之后发现确实比我更有规律...妙啊妙啊 我的:1#include<bits/stdc++.h>2usingnamespacestd;34longlongintn,k,m=1,p=0;//k表示增加......
  • 面向对象编程
    内容概要面对对象核心思路编程思想面向对象之类与对象类与对象的创建对象独有的数据(属性)对象独有的功能(方法)面对对象核心思路我们如果没有对象的存在,我们有一些......
  • shell编程之数组
    1什么是数组数组(Array)是有序的元素序列。若将有限个类型相同的变量的集合命名,那么这个名称为数组名。组成数组的各个变量称为数组的分量,也称为数组的元素,有时也称为下......