精华内容
下载资源
问答
  • Lock锁底层依赖于AQS实现,AQS提供了多种锁的实现模式,其中独占锁共享锁是主要的两种模式。AQS本身是一种模板方法设计模式,即AQS对外部提供了一些模板方法,而这些模板方法又会调用由子类实现的抽象方法。今天...

    Lock锁底层依赖于AQS实现,AQS提供了多种锁的实现模式,其中独占锁和共享锁是主要的两种模式。AQS本身是一种模板方法设计模式,即AQS对外部提供了一些模板方法,而这些模板方法又会调用由子类实现的抽象方法。今天我们主要是比较AQS中共享锁和独占锁的底层实现方面的不同。

    public final void acquire(int arg){/*对外提供的独占锁的模板方法*/ public final void acquireShared(int arg){ //对外提供的共享锁的模板方式

    if(!tryAcquire(arg) if(tryAcquireShared(arg)<0)

    &&acquireQueued(addWaiter(Node.EXCLUSIVE),arg)) doAcquireShared(arg);

    selfInterrupt()/*中断当前调用线程*/ }

    }

    先来分析acuqire(arg)方法,首先我们要理解java中的短路运算符&&,也就是说当tryAcquire(arg)方法返回false时,即获取锁失败时,才会执行acquireQueued(addWaiter(Node.EXCLUSIVE),arg),剖开语句acquireQueued(**),先执行addWaiter(Node.EXCLUSIVE),然后执行acquireQueued(),所以一句if基本上就调用了所有的后续处理,这种编码方式,在java源码实现中非常常见。相比之下,acquireShared(arg)方法更加符合我们平时的编码习惯。

    addWaiter方法的目的是将未成功获取到锁的线程中加入到同步队列中去,先看源码:

    private Node addWaiter(Node mode){ private Node enq(final Node node){

    Node node=new Node(Thread.currentThread(),mode); for(;;){

    Node pred=tail; Node t=tail;

    if(pred!=null){ if(t==null){

    node.prev=pred; if(compareAndSetHead(new Node()))

    if(compareAndSetTail(pred,node)){/*注意该方式是原子方式*/ tail=head;

    pred.next=node; }else{

    return node; node.prev=t;

    } if(compareAndSetTail(t,node)){

    } t.next=node;

    enq(node); return t;

    return node; }

    } }

    }

    }

    上述的addWaiter方法首先构造一个新的节点,并先尝试插入同步队列,如果成功后,直接返回,如果不成功,则调用enq方法进行循环插入。节点既然已经被加入到同步队列中去了,那么接下来就需要将线程阻塞,阻塞之前需要再次尝试获取锁,如果仍然失败则阻塞,具体的处理方法在acquireQueued(node,arg);

    final boolean acquireQueued(final Node node,int arg){

    boolean failed=true;

    try{

    boolean interrupted=false;

    for(;;){

    final Node p=node.predecessor();

    if(p==head&&tryAcquire(arg)){

    setHead(node);//注意这一段代码并没有进行并发控制,因为这一句是由获取锁的线程设置,所以不需要进行同步控制

    p.next=null;

    failed=false;

    return interrupted;

    }

    if(shouldParkAfterFailedAcquire(p,node)&&parkAndCheckInterrupt())

    interrupted=true;

    }

    }finally{

    if(failed)

    cancelAcquire(node);

    }

    }

    在上述代码中,关键的一点是shouParkAfterFailedAcquire方法和parkAndCheckInterrupt方法,接下来我们看下这两个函数的源码实现:

    private static boolean shouldParkAfterFailedAcquire(Node pred,Node node){

    int ws=pred.waitStatus;

    if(ws==Node.SIGNAL) return true;// SIGNAL表示该节点的后继节点正在阻塞中,当该节点释放时,将唤醒后继节点。此时node可以安全地进行阻塞,因为可以保证会被唤醒

    if(ws>0){//表示前置节点已经被取消

    do{//循环找到一个未被取消的节点

    node.prev=pred=pred.prev;

    }while(pred.waitStatus>0);

    pred.next=node; //执行到这一句时,acquireQueued方法会循环一次,再次尝试获取锁

    }else{

    compareAndSetWaitStatus(pred,ws,Node.SIGNAL);

    }

    return false;

    }

    规则1:如果前继的节点状态为SIGNAL,表明当前节点可以安全地进行阻塞,则返回成功,此时acquireQueued方法的第12行(parkAndCheckInterrupt)将导致线程阻塞

    规则2:如果前继节点状态为CANCELLED(ws>0),说明前置节点已经被放弃,则回溯到一个非取消的前继节点,返回false,acquireQueued方法的无限循环将递归调用该方法,直至规则1返回true,导致线程阻塞

    规则3:如果前继节点状态为非SIGNAL、非CANCELLED,则设置前继的状态为SIGNAL,返回false后进入acquireQueued的无限循环,与规则2同

    下面我们再来分析一下,共享锁acquireShared()方法中的doAcquireShared(arg),调用该方法说明,共享锁已经用完了,当前线程需要进行等待重新获取:

    private void doAcquireShared(int arg){

    final Node node=addWaiter(Node.SHARED);//构造一个新的节点,并将新的节点加入到同步队列中

    boolean failed=true;

    try{

    boolean interrupted=false;

    for(;;){

    final Node p=node.predecessor();

    if(p==head){

    int r=tryAcquireShared(arg);//再次尝试获取共享锁

    if(r>=0){

    setHeadAndPropagate(node,r);//这一句很关键

    p.next=null;

    if(interrupted) selfInterrupt();

    failed=false;

    return;

    }

    if(shouldParkAfterFailedAcquire(p,node)&&parkAndCheckInterrupt())//同独占锁的规则一样

    interrupted=true;

    }

    }

    }finally{

    if(failed)

    cancelAcquire(node);

    }

    }

    上面的代码中主要的一句关键代码是setHeadAndPropagate方法,主要能够调用setHeadAndPropagate方法,说明当前线程已经活到了锁,下面我们来看看这句代码的实现:

    private void setHeadAndPropagate(Node node,int propagate){

    Node h=head;

    setHead(node);//因为有多个线程可能同时获取了共享锁,setHead方法可能会设置不成功,不过已经获取了锁,也不用关心是否设置成功

    if(propagate>0||h==null||h.waitStatus<0){

    Node s=node.next;

    if(s==null||s.isShared())

    doReleaseShared();

    }

    }

    独占锁某个节点被唤醒之后,它只需要将这个节点设置成head就完事了,而共享锁不一样,某个节点被设置为head之后,如果它的后继节点是SHARED状态的,那么将继续通过doReleaseShared方法尝试往后唤醒节点,实现了共享状态的向后传播。

    展开全文
  • 1.乐观--乐观是一种思想,它只解决对共享资源更新时的一致性问题,不解决读取共享资源过程中,其他线程修改了共享资源导致读取的是旧的资源的问题一般范式为:private Node enq(finalNode node) {for(;...

    1.乐观锁--乐观锁是一种思想,它只解决对共享资源更新时的一致性问题,不解决读取共享资源过程中,其他线程修改了共享资源导致读取的是旧的资源的问题

    一般范式为:

    private Node enq(finalNode node) {for(;;) {

    Node t=tail;if (t == null) { //Must initialize

    if (compareAndSetHead(newNode()))

    tail=head;

    }else{

    node.prev=t;if(compareAndSetTail(t, node)) {

    t.next=node;returnt;

    }

    }

    }

    }

    for(;;){

    更新期望共享资源;if(期望共享资源==现实共享资源){

    update();return}

    }

    20181128101108299604.png

    这种实现存在如下问题1):ABA问题----假如是链表结构,1线程操作期间,其他线程修改了A.next,1线程比较后自然以为是预期值,判断true,继续操作(解决办法加入版本号标识,比较的不仅仅是A的值还有A的版本号)

    2):自旋导致cpu压力

    2.悲观锁--总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。应用场景可以抽象成如下图

    20181128101108706846.png

    可重入锁:获取到外函数的锁时,内部函数的锁也获取到了

    独占锁--每次只有一个线程能拿到锁(比如synchronize 和reentrantLock)如上图

    共享锁--多个线程拿到锁(ReentrantReadWriteLock )如下图,3个读线程都获取锁,但是不允许读写线程和写写线程同时都持有锁,理由很简单。

    20181128101109025217.png

    公平锁--后续等待线程是一个FIFO队列,先排队的先获取锁

    非公平锁--线程不排队(默认,效率高)

    展开全文
  • 非公平 是指多个线程获取的顺序并非按照申请的顺序,有可能后申请的线程比先申请的线程优先获取在高并发的状况下,有可能会形成优先级反转或者饥饿现象web区别:非公平的优势在于吞吐量比公平大。...

    公平和非公平锁

    公平锁 是指多个线程按照申请锁的顺序来获取锁,相似排队打饭,先来后到。

    非公平锁 是指多个线程获取锁的顺序并非按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁在高并发的状况下,有可能会形成优先级反转或者饥饿现象web

    区别:非公平锁的优势在于吞吐量比公平锁大。缓存

    Synchronized、ReentrantLock(默认)是非公平锁,ReentrantLock能够手动设置为公平锁,参数设为true便可。并发

    Lock lock=new ReentrantLock(true);

    可重入锁 (又称递归锁)

    可重入锁(也叫作递归锁)指的是同一线程外层函数得到锁以后,内层递归函数仍然能获取该锁的代码,在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁也便是说,线程能够进入任何一个它已经拥有的锁所同步着的代码块。svg

    ReentrantLock、Synchronized就是一个典型的可重入锁函数

    可重入锁最大的做用是避免死锁高并发

    例如;method01和method02都上了锁,同一个线程 调用 method01时,内存函数method02仍然加锁,他们是同一把锁。

    3c6ce88d2dd67eda79f1c90abb179757.pngatom

    自旋锁

    自旋锁是指尝试获取锁的线程不会当即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减小线程上下文切换的消耗,缺点是循环会消耗CPU

    2f5909e81bc178150a1ce920fe07b55d.png

    手写自旋锁线程

    public class DemoTest {

    //原子引用线程

    AtomicReference atomicReferenc=new AtomicReference<>();

    public void mylock(){

    Thread thread=Thread.currentThread();

    System.out.println(Thread.currentThread().getName()+"\t come in (*^▽^*)");

    //进行自旋操做 判断指望的值 为 null , 若是是将thread更新 返回true 结果取反

    while (!atomicReferenc.compareAndSet(null,thread)){

    }

    }

    public void myUnLock(){

    Thread thread=Thread.currentThread();

    // 释然线程, 若是为当前线程,则更新为 null

    atomicReferenc.compareAndSet(thread,null);

    System.out.println(Thread.currentThread().getName()+"\t invoked myUnLock()");

    }

    public static void main(String[] args) {

    DemoTest demoTest=new DemoTest();

    new Thread(()->{

    demoTest.mylock();

    try {

    //睡眠5秒

    TimeUnit.SECONDS.sleep(5);

    } catch (InterruptedException e) {

    e.printStackTrace();

    }

    demoTest.myUnLock();

    },"aa").start();

    try {

    TimeUnit.SECONDS.sleep(1);

    } catch (InterruptedException e) {

    e.printStackTrace();

    }

    new Thread(()->{

    demoTest.mylock();

    demoTest.myUnLock();

    },"bb").start();

    }

    }

    共享锁 和 独占锁

    独占锁(写锁):指该锁一次只能被一个线程所持有。对ReentrantLock和Synchronized而言都是独占锁3d

    共享锁(读锁):指该锁可被多个线程所持有。

    对ReentrantReadWriteLock其读锁是共享锁,其写锁是独占锁。

    读锁的共享锁可保证并发读是很是高效的,读写,写读,写写的过程是互斥的。code

    使用独占共享锁实现建议缓存(基于ReentrantReadWriteLock)

    public class MyCache {

    private volatile Map map = new HashMap<>();

    ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock();

    public void put(String key, Object value) {

    //独占锁(写锁)

    rwlock.writeLock().lock();

    try {

    System.out.println(Thread.currentThread().getName() + "\t 正在写入: " + key);

    try {

    //暂停一会线程

    TimeUnit.MILLISECONDS.sleep(300);

    } catch (Exception e) {

    e.printStackTrace();

    }

    map.put(key, value);

    System.out.println(Thread.currentThread().getName() + "\t 写入完成");

    } finally {

    rwlock.writeLock().unlock();

    }

    }

    public void get(String key) {

    //共享锁(读锁)

    rwlock.readLock().lock();

    try {

    System.out.println(Thread.currentThread().getName() + "\t 正在读取: " + key);

    try {

    TimeUnit.MILLISECONDS.sleep(300);

    } catch (Exception e) {

    e.printStackTrace();

    }

    Object result = map.get(key);

    System.out.println(Thread.currentThread().getName() + "\t 读取完成" + result);

    } finally {

    rwlock.readLock().unlock();

    }

    }

    public static void main(String[] args) {

    MyCache myCache = new MyCache();

    for (int i = 0; i <= 5; i++) {

    final int tempInt = i;

    new Thread(() -> {

    myCache.put(tempInt + "", tempInt + "");

    }, String.valueOf(i)).start();

    }

    for (int i = 0; i <= 5; i++) {

    final int tempInt = i;

    new Thread(() -> {

    myCache.get(tempInt + "");

    }, String.valueOf(i)).start();

    }

    }

    }

    展开全文
  • Java并发编程独占公平与非公平比较公平和非公平理解:在上一篇文章中,我们知道了非公平。其实Java中还存在着公平呢。公平二字怎么理解呢?和我们现实理解是一样的。大家取排队本着先来先得到的原则...

    Java并发编程锁之独占公平锁与非公平锁比较

    公平锁和非公平锁理解:

    在上一篇文章中,我们知道了非公平锁。其实Java中还存在着公平锁呢。公平二字怎么理解呢?和我们现实理解是一样的。大家取排队本着先来先得到的原则,在排队中,无论身份贵贱,一律平等对待。这是就是我们现实生活中的公平。大家都喜欢公平的。但是在Java中默认是非公平的,为什么呢?

    本文主要内容:公平锁的现实生活理解;公平锁演示;为什么Java中默认是非公平锁(公平锁的非公平锁的比较)

    本篇是《凯哥(凯哥Java:kagejava)并发编程学习》系列之《Lock系列》教程的第四篇:《Java并发包下锁学习第五篇:公平锁理解及与非公平锁的比较》。

    生活中的例子:

    同样还是去ATM机取钱的例子。假设现在有3个人使用ATM取钱。路人甲不会用ATM,自己摸索耗时5min,然后终于学会怎么使用了,但是密码又忘掉了。打电话给家里人咨询耗时1min.当路人甲操作完成之后,后面两个人排队接着依次操作,这种方式是谁先到谁先操作,操作完成之后下一个人才可以操作的,不管贫富贵贱,不管你是取100还是取1W,取1W的人在取100的人后面,就要排着队等着,这种看上去很公平的,无论贵贱,大家依次操作,这种操作模式站在多线程并发角度来看的话,就是公平锁操作。

    在路人甲总耗时6min之后,路人乙和路人丁两个人操作耗时3min。也就是三个人总耗时9min.为什么会产生这种情况呢?因为路人甲堵着了。一直占着锁的资源。在路人甲操作的过程中,其他人只能排队等待。如果路人甲不会操作,排在他后面的路人丙插队询问路人甲,自己可以先插队操作ATM,同时教路人甲。如果甲同意,则丙可以操作完成后,甲可以学着别人操作,有可能路人甲2min也能操作完成。这个时候,三个人总耗时就是5min了;如果甲不同意丙插队操作,那么丙只能回到原来位置上,排队等待。这种操作模式站在多线程并发角度来考虑的话,路人甲在模式及和家人通话耗时看着是CPU切换上下文的耗时。路人丙插队后获取ATM资源,这个操作可以看着是非公平的,因为丙的进入时间比路人乙晚,但是丙先操作了。但是从最后三人总耗时来看,路人丙插队,是的效率提高了。这种操作在Java并发中,称之为非公平锁。

    需要说明的是,无论是显式锁还是隐式锁默认都是非公平的。因为非公平能够提升系统的吞吐量。

    非公平锁的定义:

    线程先尝试着获取同步状态操作(丙先尝试着插队),如果获取到,就对共享变量操作(甲同意丙的插队,丙就操作ATM机),如果获取不到,就接着排对。

    使用方法二:独占公平演示

    需求:控制台打印的结果和线程顺序一致。代码如下:

    f711a11fcd2e3b33d7956e5272fb9b1c.png

    此时代码和上一次代码唯一区别如上图:在实例化lock的是有个参数,设置了true.

    运行结果:

    4edc210023c9dfd26ed3198da6b5373c.png

    从运行结果中,我们发现控制台打印出的获取锁的顺序和调用锁的时候顺序是一样的, 已经达到我们预期结果了。但是,还是每次只有一个线程操作,等这个线程操作完成释放锁后,其他线程才可以接着获取锁。

    公平锁与非公平锁的比较

    问题:

    为什么并发大师Doug Lea把ReentrantLock设计默认模式是非公平的?

    其实要回答这个问题,就需要从公平锁与非公平锁的不同来进行比较了。我们先来看看ATM机操作在公平锁和非公平锁的场景下,如下图:

    2a5c5e39667565c5a58c71ad1a0a45d0.png

    公平锁:大家都排队,如果一个线程堵着了(路人甲),其他线程只能等待这个。最终,三个线程操作完成,总耗时9min.

    非公平锁情况下:多个线程操作的共享资源的时候,发现共享资源还没有被锁定(路人甲还在摸索过程),就尝试插队(路人丙尝试和甲沟通,先插队操作并教会甲),如果插队成功(甲同意了),就操作共享资源(丙先操作ATM机);如果插队失败(甲不同意),接着排对(丙回到队伍中排对)。如果插队成功,最终耗时:5min.

    从中我们可以看出公平锁和非公平锁的优缺点了。

    优缺点比较:

    非公平锁:

    优点:效率高;缺点:容易导致线程“饥饿”。当多个线程使用非公平的话,有可能有一个线程一直就获取不到竞争权,导致这个线程会“饥饿而死”。

    适用场景:

    如果在不考虑TPS(单位时间内成功完成的次数)作为唯一考量指标的场景下,可以使用非公平锁来操作,因为非公平锁能提高系统的吞吐量;

    公平锁:

    优点:避免了线程的“饥饿”;缺点:性能相对于公平锁会差很多。

    79007506114945ae7f4e6c0b5f5bed69.png

    展开全文
  • 一、公平与非公平1.1 概述公平:是指多个线程按照申请的顺序来获取。非公平:是指在多线程获取的顺序并不是按照申请的顺序,有可能后申请的线程比先申请的线程优先获取到,在高并发的情况下,有...
  • 可重入不可重入/*** @ClassName ReenterLockDemo* @Description 可重入(也叫递归)** 指的是同一线程 外层函数获得之后,内层递归函数仍然能获得该的代码* 在同一个线程的外层方法获取的时候,进入内层...
  • 共享锁 所有共享锁持有者可读  FileOutputStream.getChannel().tryLock(0L, Long.MAX_VALUE, true)获得共享锁, NonReadableChannelException异常,共享锁可读不可写,获取共享锁,必须要包含read channel   ...
  • 独占锁 / 共享锁、读写锁独占锁共享锁读写锁案例如下 独占锁和共享锁 独占锁: 指该锁一次只能被一个线程所持有。对ReentrantLockSynchronized而言都是独占锁。 共享锁: 指該锁可被多个线程所持有。 读写锁 ...
  • 共享锁独占锁 独占锁:也是悲观锁 synchronized和ReentrantLock 共享锁接口: ReadWriteLock接口 共享锁:该锁可被多个线程共有,典型的就是ReentrantReadWriteLock里的读锁,它的读锁是可以被共享的,但是它的写...
  • 共享锁独占锁 独占锁独占锁也叫排他锁,是指该锁一次只能被一个线程所持有。如果线程对数据A加上排他锁后,则其他线程不能再对A加任何类型的锁。获得排它锁的线程即能读数据又能修改数据。 共享锁共享锁是指该...
  • AQS共享锁独占锁对比
  • 独占锁共享锁

    千次阅读 2019-08-09 19:38:11
    独占锁共享锁前言概念引入独占锁概念共享锁概念源码分析ReentrantReadWriteLock源码读锁和写锁的具体加锁方式有什么区别 前言 独占锁共享锁同样是一种概念。我们先介绍一下具体的概念,然后通过ReentrantLock和...
  • (1) 从数据库系统的角度来看锁分为以下三种类型: •独占锁(Exclusive Lock)独占锁锁定的资源只允许进行锁定操作的程序使用,其它任何对它的操作均不会被接受。执行数据更新命令,即INSERT、 UPDATE 或DELETE ...
  • 共享锁独占锁

    2012-04-29 17:27:34
    共享锁独占锁的经典应用,是控制最初用于读取的共享文件的更新。某个进程要读取文件,会先取得该文件或该文件部分区域的共享锁。第二个希望读取相同文件区域的进程也会请求共享锁。两个进程可以并行读取,互不影响...
  • title: mysql共享锁和排它锁author: Joe Tongtags:JAVAEEMYSQLcategories:ITdate: 2020-05-29 15:35:53一、相关名词|--表级锁(锁定整个表)|--页级锁(锁定一页)|--行级锁(锁定一行)|--共享锁(S锁,MyISAM 叫做读锁)|-...
  • java中有公平锁、非公平锁、可重入锁(递归锁)、自旋锁、独占锁(写锁)、共享锁(读锁)……编程公平锁 和 非公平锁并发是什么:公平锁:是指多个线程按照申请的顺序来获取值函数非公平锁:是值多个线程获取值的顺序并非...
  • 独占锁共享锁

    2018-01-06 15:00:41
    若某事务在数据上持有独占锁定(X), 则其他会话不能读写数据,其他事务不能对该...若某事务在数据上持有共享锁(S),则其他会话只能读取数据、不能写数据,其他事务只能申请对该数据的共享锁、而不能申请独占锁
  • 对ReentrantLock和Synchronized而言都是独占锁共享锁:指该锁可被多个线程所持有。对ReentrantReadWriteLock其读锁是共享锁,其写锁是独占锁。读锁的共享锁可保证并发读是非常高效的,读写,写读,写写的过程是互斥...
  • 独占锁(写锁) / 共享锁(读锁) / 互斥锁概念独占锁:指该锁一次只能被一个线程所持有。对ReentrantLock和Synchronized而言都是独占锁共享锁:指该锁可以被多个线程锁持有对ReentrantReadWriteLock其读锁是共享,其写...
  • 将这两个锁之前,我先讲下上一章遗留的...共享锁独占锁 共享锁,顾名思义,就是这个锁是共享的,咱们能一起使用这个锁。这个就像进男厕所,以前的那种小便池,可以站很多人。 一般共享锁,使用是在读数据的基础上加
  • 个人理解记录ReentrantLock基于aqs...一般用try finally来实现,相对于synchronized,reentrantlock提供了功能更强大的api,例如超时锁、可中断锁、公平锁、非公平锁、非阻塞锁获取等等,ReentrantLock是独占锁,...
  • 1.乐观--乐观是一种思想,它只解决对共享资源更新时的一致性问题,不解决读取共享资源过程中,其他线程修改了共享资源导致读取的是旧的资源的问题 一般范式为: private Node enq(final Node node) { for ...
  • 共享锁独占锁简单介绍 共享锁:Shared Locks,简称S锁,可以多个事务共同持有,获得共享锁的事务只能读数据,不能修改数据。 假如事务1⾸先获取了⼀条记录的S锁,如果事务2想要获取这条记录的S锁,那么事务T2也能...
  • Java 中 ReentrantReadWriteLock 其读锁是共享锁,其写锁是独占锁。 读锁的共享锁可保证并发读是非常高效的,读写,写读,写写的过程是互斥的。 互斥锁 在访问共享资源之前对进行加锁操作,在...
  • 我们经常听到的独占锁其实就是写锁:一次只能被一个线程占有;共享锁其实就是读锁:多个线程可以同时占有 我们举个例子,先来看看不加读写锁的情况 package com.lt.ReadWriteLock; import java.util.HashMap; ...
  • 读写锁ReadWriterLock可以“分离”出两个“子锁” 读写分离 读 写 1、读————共享锁 多个线程可以一起读取,不会改变数据 2、写————排他锁 多个线程可以一起写 写会改变

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,966
精华内容 1,186
关键字:

共享锁独占锁