精华内容
下载资源
问答
  • 独占共享锁

    千次阅读 2019-08-09 19:38:11
    独占共享锁前言概念引入独占概念共享锁概念源码分析ReentrantReadWriteLock源码读和写的具体加锁方式有什么区别 前言 独占共享锁同样是一种概念。我们先介绍一下具体的概念,然后通过ReentrantLock和...

    前言

    独占锁和共享锁同样是一种概念。我们先介绍一下具体的概念,然后通过 ReentrantLock 和 ReentrantReadWriteLock 的源码来介绍独占锁和共享锁。

    概念引入

    独占锁概念

    独占锁也叫排他锁,是指该锁一次只能被一个线程所持有。如果线程T对数据A加上排他锁后,则其他线程不能再对A加任何类型的锁。获得排它锁的线程即能读数据又能修改数据。JDK中的synchronized和 JUC中Lock的实现类就是互斥锁。

    共享锁概念

    共享锁是指该锁可被多个线程所持有。如果线程T对数据A加上共享锁后,则其他线程只能对A再加共享锁,不能加排它锁。获得共享锁的线程只能读数据,不能修改数据。 独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。

    源码分析

    ReentrantReadWriteLock源码

    在这里插入图片描述
    我们看到 ReentrantReadWriteLock 有两把锁:ReadLock和WriteLock,见名知意,一个读锁一个写锁, 合称“读写锁”。
    再进一步观察可以发现 ReadLock 和 WriteLock 是靠内部类 Sync 实现的锁。
    Sync 是 AQS 的一个子类,这种结构在 CountDownLatch 、ReentrantLock 、Semaphore 里面也都存在。
    在ReentrantReadWriteLock 里面,读锁和写锁的锁主体都是 Sync ,但读锁和写锁的加锁方式不一样。
    读锁是共享锁,写锁是独占锁。读锁的共享锁可保证并发读非常高效,而读写、写读、写写的过程互斥,因为读锁和写锁是分离的。所以ReentrantReadWriteLock的并发性相比一般的互斥锁有了很大提升。

    读锁和写锁的具体加锁方式有什么区别

    在了解源码之前我们需要回顾一下其他知识。 在最开始提及 AQS 的时候我们也提到了state字段(int类型,32位),该字段用来描述有多少线程获持有锁。 在独享锁中这个值通常是0或者1(如果是重入锁的话state值就是重入的次数),在共享锁中state就是持有锁的数量。但是在 ReentrantReadWriteLock 中有读、写两把锁,所以需要在一个整型变量state上分别描述读锁和写锁的数量(或者也可以叫状态)。于是将state变量“按位切割”切分成了两个部分,高16位 表示读锁状态(读锁个数),低16位表示写锁状态(写锁个数)。如下图所示:
    在这里插入图片描述

    protected final boolean tryAcquire(int acquires) {
    	Thread current = Thread.currentThread();
    	int c = getState(); // 取到当前锁的个数
    	int w = exclusiveCount(c); // 取写锁的个数w
    	if (c != 0) { // 如果已经有线程持有了锁(c!=0)
    		// (Note: if c != 0 and w == 0 then shared count != 0)
    		if (w == 0 || current != getExclusiveOwnerThread()) // 如果写线程数(w)为0(换言之存在读锁) 或者持有锁的线程	不是当前线 程就返回失败
    				return false;
    		if (w + exclusiveCount(acquires) > MAX_COUNT) // 如果写入锁的数量大于最大数(65535,2的16次方-1)就抛出一个Error。
    		throw new Error("Maximum lock count exceeded");
    // Reentrant acquire
    		 setState(c + acquires);
    		return true;
     }
    if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) // 如果当且写线程数为0,并且当前线程需要阻塞那么就返回失败;或者如果通过CAS增加写线程数失败也返回失败。
    		return false;
    	setExclusiveOwnerThread(current); // 如果c=0,w=0或者c>0,w>0(重入),则设置当前线程或锁的拥有者
    	return true; 
    }
    
    • 这段代码首先取到当前锁的个数c,然后再通过c来获取写锁的个数w。因为写锁是低16位,所以取低16位的最大值与当前的c做与运算( int w = exclusiveCount©; ),高16位和0与运算后是0,剩下的就是低位运算的值,同时也是持 有写锁的线程数目。
    • 在取到写锁线程的数目后,首先判断是否已经有线程持有了锁。如果已经有线程持有了锁(c!=0),则查看当前写锁线程的数目,如果写线程数为0(即此时存在读锁)或者持有锁的线程不是当前线程就返回失败。
    • 如果写入锁的数量大于最大数(65535,2的16次方-1)就抛出一个Error。
    • 如果当且写线程数为0(那么读线程也应该为0,因为上面已经处理c!=0的情况),并且当前线程需要阻塞那么就返 回失败;如果通过CAS增加写线程数失败也返回失败。
    • 如果c=0,w=0或者c>0,w>0(重入),则设置当前线程或锁的拥有者,返回成功!

    tryAcquire()除了重入条件(当前线程为获取了写锁的线程)之外,增加了一个读锁是否存在的判断。
    如果存在读锁,则写锁不能被获取,原因在于:
    必须确保写锁的操作对读锁可见,如果允许读锁在已被获取 的情况下对写锁的获取,那么正在运行的其他读线程就无法感知到当前写线程的操作。
    因此,只有等待其他读线程都释放了读锁,写锁才能被当前线程获取,而写锁一旦被获取,则其他读写线程的后续访问均被阻塞。写锁的释放与 ReentrantLock 的释放过程基本类似,每次释放均减少写状态,当写状态为0时表示写锁已被释放,然后等待的读写线程才能够继续访问读写锁,同时前次写线程的修改对 后续的读写线程可见。 接着是读锁的代码:

    protected final int tryAcquireShared(int unused) {
     	Thread current = Thread.currentThread();
    	int c = getState();
    	if (exclusiveCount(c) != 0 &&getExclusiveOwnerThread() != current)
    		return -1; // 如果其他线程已经获取了写锁,则当前线程获取读锁失败,进入等待状态
    	int r = sharedCount(c);
    	if (!readerShouldBlock() &&r < MAX_COUNT &&compareAndSetState(c, c + SHARED_UNIT)) {
    		if (r == 0) {
     			firstReader = current;
     			firstReaderHoldCount = 1;
     		} else if (firstReader == current) {
     			firstReaderHoldCount++;
     		} else {
     			HoldCounter rh = cachedHoldCounter;
    			if (rh == null || rh.tid != getThreadId(current))
     				cachedHoldCounter = rh = readHolds.get();
    			else if (rh.count == 0)
     				readHolds.set(rh);
     				rh.count++;
     		}
    		return 1;
     	}
    	return fullTryAcquireShared(current);
    }
    

    可以看到在 tryAcquireShared(int unused) 方法中,如果其他线程已经获取了写锁,则当前线程获取读锁 失败,进入等待状态。如果当前线程获取了写锁或者写锁未被获取,则当前线程(线程安全,依靠CAS保证)增加读状态,成功获取读锁。读锁的每次释放(线程安全的,可能有多个读线程同时释放读锁)均减少读状态,减少的值是“1<<16”。所以读写锁才能实现读读的过程共享,而读写、写读、写写的过程互斥。

    展开全文
  • 传统的关系数据库里就用到了很多这种机制,比如行锁,表锁,共享锁,排他等,都是在做操作之前先上。   行锁: 下面演示行锁,打开两个mysql命令行界面,两个线程分别执行如下操作:(左边先执行)

    悲观锁:

      顾名思义,很悲观,就是每次拿数据的时候都认为别的线程会修改数据,所以在每次拿的时候都会给数据上锁。上锁之后,当别的线程想要拿数据时,就会阻塞,直到给数据上锁的线程将事务提交或者回滚。传统的关系型数据库里就用到了很多这种锁机制,比如行锁,表锁,共享锁,排他锁等,都是在做操作之前先上锁。
      
    行锁:

      下面演示行锁,打开两个mysql命令行界面,两个线程分别执行如下操作:(左边先执行)
      
      image_1b8uapk3h3pk1kmmpkg1av0v759.png-174kB
      
      左边的线程,在事务中通过select for update语句给sid = 1的数据行上了锁。右边的线程此时可以使用select语句读取数据,但是如果也使用select for update语句,就会阻塞,使用update,add,delete也会阻塞。
      当左边的线程将事务提交(或者回滚),右边的线程就会获取锁,线程不再阻塞:
      
      image_1b8ub488p128jhm911gka0hhhm.png-201.2kB
      
      此时,右边的线程获取锁,左边的线程如果执行类似操作,也会被阻塞:
      
      image_1b8ub6ptr1i0n1j8hg681fir1fnb13.png-236.3kB
      
    表锁:
      
      上述例子中,如果使用如下语句就是使用的表锁:

    select * from student for update;

    页锁:
      
      行锁锁指定行,表锁锁整张表,页锁是折中实现,即一次锁定相邻的一组记录。
      
    共享锁:
      
      共享锁又称为读锁,一个线程给数据加上共享锁后,其他线程只能读数据,不能修改。
      
    排他锁:
      
      排他锁又称为写锁,和共享锁的区别在于,其他线程既不能读也不能修改。
      
    乐观锁:
      
      乐观锁其实不会上锁。顾名思义,很乐观,它默认别的线程不会修改数据,所以不会上锁。只是在更新前去判断别的线程在此期间有没有修改数据,如果修改了,会交给业务层去处理。
      常用的实现方式是使用版本戳,例如在一张表中添加一个整型字段version,每更新version++,比如某个时刻version=1,线程A读取了此version=1,线程B也读取了此version=1,当线程A更新数据之前,判断version仍然为1,更新成功,version++变为2,但是当线程B再提交更新时,发现version变为2了,与之前读的version=1不一致,就知道有别的线程更新了数据,这个时候就会进行业务逻辑的处理。
      
    通常情况下,写操作较少时,使用乐观锁,写操作较多时,使用悲观锁。

    展开全文
  • 共享锁和排它

    2020-06-04 23:42:40
    共享锁和排它 以 ReentranReadWriteLock 读写为例 什么是共享锁和排它 排它,又称独占,独享 synchronized就是一个排它 共享锁,又称为读,获得共享锁后,可以查看,但无法删除和修改数 ...

    共享锁和排它锁

    以 ReentranReadWriteLock 读写锁为例

    什么是共享锁和排它锁

    • 排它锁,又称独占锁,独享锁 synchronized就是一个排它锁

    • 共享锁,又称为读锁,获得共享锁后,可以查看,但无法删除和修改数 据, 其他线程此时业获取到共享锁,也可以查看但是 无法修改和 删除数据

    • 共享锁和排它锁典型是ReentranReadWriteLock 其中,读锁是共享锁,写锁是 排它锁

    读写锁的作用

    在没有读写锁之前,我们使用的是ReentrantLock ,虽然我们保证了线程安全,,但是业浪费了一定的资源,多个读操作同时进行,是不会出现线程安全问题

    读写锁的规则

    • 读写锁只是一把锁,可以通过2种方式锁定,读锁定和写锁定

    • 多个线程取请求读锁,都可以请求到的
      如果一个线程获取到了读锁,其他线程需要写锁,申请的写锁必须得等读锁释放
      总结 : 要么多读,要么一写,二者不可共存

    ReentranReadWriteLock 具体用法

    上代码

    package com.yxl.po;
    
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    public class a {
    
        //创建读写锁
        private static ReentrantReadWriteLock reentrantReadWriteLock=new ReentrantReadWriteLock();
    
        //创建读锁
        private static ReentrantReadWriteLock.ReadLock readLock=reentrantReadWriteLock.readLock();
        //创建写锁
        private static ReentrantReadWriteLock.WriteLock writeLock=reentrantReadWriteLock.writeLock();
    
        //读锁方法
        private static void read(){
            readLock.lock();
            try {
                System.out.println(Thread.currentThread().getName()+"得到了读锁");
                Thread.sleep(1000);
            }catch (Exception e){
    
            }finally {
                System.out.println(Thread.currentThread().getName()+"释放读锁");
                readLock.unlock();
            }
        }
    
        //写锁方法
        private static void write(){
            writeLock.lock();
            try {
                System.out.println(Thread.currentThread().getName()+"得到了写锁");
                Thread.sleep(1000);
            }catch (Exception e){
    
            }finally {
                System.out.println(Thread.currentThread().getName()+"释放写锁");
                writeLock.unlock();
            }
        }
    
        public static void main(String[] args) {
            new Thread(()->read(),"T1").start();
            new Thread(()->read(),"T2").start();
            new Thread(()->write(),"T3").start();
            new Thread(()->write(),"T4").start();
        }
    
    
    }
    
    
    

    在这里插入图片描述

    • 从运行结果可以、看到 读锁 线程1 和 2 都可以一起获取到 而写锁必须得获取释放,下一个线程才能获取

    读锁和写锁的交互方式

    • 插队: 不允许读锁插队
    • 升降级 允许降级,不允许升级,

    是否是公平策略 ,和 ReentranLock 一样 传入true ,false

    在这里插入图片描述

    • 底层设计思路和ReentranLock 一样

    在这里插入图片描述

    公平锁: 不允许插队
    非公平锁:

    • 写锁可以随时插队
    • 读锁仅在等待队列头节点不是想获取写锁到时候可以插队
    展开全文
  • MySQL 共享锁和排他

    2020-09-27 09:33:12
    排他又称为写,简称X,顾名思义,排他就是不能与其他所并存,如一个事务获取了一个数据行的排他,其他事务就不能再获取该行的其他,包括共享锁和排他,但是获取排他的事务是可以对数据读取和修改。...

    一、共享锁和排他锁

    共享锁又称为读锁,简称D锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。

    排他锁又称为写锁,简称X锁,顾名思义,排他锁就是不能与其他所并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据读取和修改。

    自己的理解


    现在一个简单的例子:

    这个表进行操作
    在这里插入图片描述

    对id = 1 进行排他锁操作,使用begin开启事务。提交事务或回滚事务就会释放锁。

    begin; -- 开启事务
    
    select * from actor where id = 1 for update; -- 进行id = 1排他锁 
    

    在这里插入图片描述

    可以查询到数据,再打开另外一个查询窗口。对同一数据进行排他锁和共享锁操作

    select * from actor where id = 1 for update; -- 排他查
    
    select * from actor where id = 1 lock in share mode; -- 共享查
    
    -- 开了排他锁查询和共享锁查询都会处于阻塞状态,因为id=1的数据已经被加上了排他锁,此处阻塞是等待排他锁释放。
    
    select * from actor where id = 1 --这个可以查询到数据
    

    一个事务获取了共享锁,在其他查询中也只能加共享锁或不加锁。

    begin;
    
    select * from actor where id = 1 lock in share mode
    
    select * from actor where id = 1; 
    select * from actor where id = 1 lock in share mode;
    -- 可以查詢到數據
    
    select * from actor where id = 1 for update;
    -- 查詢不到數據 ,因为排他锁与共享锁不能存在同一数据上。
    

    验证下上面说的mysql InnoDb引擎中update,delete,insert语句自动加排他锁的问题,

    begin;
    
    update actor set name = 'aa' where id = 1;
    
    select * from actor where id = 1 lock in share mode;
     -- 此时共享查询处于阻塞,等待排它锁的释放,但是用普通查询能查到数据,因为没用上锁机制不与排他锁互斥,但查到的数据是修改数据之前的老数据。
    
    select * from actor where id = 1; -- 可查到數據
    
    然后我们提交数据,释放排他锁看下修改后的数据,此时可用排他查,共享查和普通查询, 因为事务提交后该行数据释放排他锁,下面就只显示普通查询。
    

    二、for update

    select … for update 语句是我们经常使用手工加锁语句。会对数据库中的表或某些行数据进行锁表,在mysql中,如果查询条件带有主键,会锁行数据,如果没有,会锁表。

    1. FOR UPDATE仅适用于InnoDB,且必须在事务处理模块(BEGIN/COMMIT)中才能生效。
    2. 要测试锁定的状况,可以利用MySQL的Command Mode(命令模式) ,开两个视窗来做测试。
    3. Myisam 只支持表级锁,InnerDB支持行级锁 添加了(行级锁/表级锁)锁的数据不能被其它事务再锁定,也不被其它事务修改。是表级锁时,不管是否查询到记录,都会锁定表。

    什么时候需要使用for update?
    借助for update语句,我们可以在应用程序的层面手工实现数据加锁保护操作。就是那些需要业务层面数据独占时,可以考虑使用for update。

    场景上,比如火车票订票,在屏幕上显示有票,而真正进行出票时,需要重新确定一下这个数据没有被其他客户端修改。所以,在这个确认过程中,可以使用for update。

    for update 悲观锁
    悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它解锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。就像for update,再比如Java里面的同步原语synchronized关键字的实现也是悲观锁。

    乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。

    希望大家可以关注下我的公众号,嘻嘻!可以加入 java闲聊群,一起唠唠嗑!
    在这里插入图片描述

    展开全文
  • AQS共享锁的实现原理

    千次阅读 2017-10-20 00:44:45
    一、AQS共享锁的实现原理前面的文章Lock的实现中分析了AQS独占的实现原理,那么接下来就分析下AQS是如何实现共享锁的。共享锁的介绍共享锁:同一时刻有多个线程能够获取到同步状态。那么它是如何做到让多个线程...
  • 最全Java详解:独享/共享锁+公平/非公平+乐观/悲观乐观 VS 悲观1.乐观2.悲观3.总之公平 VS 非公平1.公平2.非公平3.典型应用独享 VS 共享锁1.独享2.共享锁3.比较4.AQS分段Java线程...
  • |--共享锁(S,MyISAM 叫做读) |--排他(X,MyISAM 叫做写) |--悲观(抽象性,不真实存在这个) |--乐观(抽象性,不真实存在这个) 二、InnoDB与MyISAM Mysql 在5.5之前默认使用 MyISAM 存储...
  • 独享/共享锁 乐观/悲观 分段 自旋 线程 乐观 VS 悲观 乐观与悲观是一种广义上的概念,体现了看待线程同步的不同角度,在Java和数据库中都有此概念对应的实际应用。 1.乐观 顾名思义...
  • 传统的关系数据库里边就用到了很多这种机制,比如行锁,表锁等,读,写等,都是在做操作之前先上。 通过 jdbc 实现时 sql 语句只要在整个语句之后加 for update 即可。例如: select...
  • 数据库锁定机制简单来说,就是数据库为了保证数据的一致性,而使各种共享资源在被并发访问变得有序所设计的一种规则。对于任何一种数据库来说都需要有相应的锁定机制,所以MySQL自然也不能例外。MySQL数据库由于其...
  • JDK AQS 共享锁部分的源码详解,详细介绍了 AQS 共享锁的加锁和释放流程
  • 搞清楚AQS独占的实现原理之后,再看共享锁的实现原理就会轻松很多。两种模式之间很多通用的地方本文只会简单说明一下,就不在赘述了,具体细节可以参考我的上篇文章深入浅出AQS之独占模式 一、执行过程概述 ...
  • 【C++】shared_ptr共享型智能指针详解

    千次阅读 2020-04-27 13:36:39
    } 尽量不要使用相同的原始指针来创建多个shared_ptr对象,因为在这种情况下,不同的shared_ptr对象不会知道它们与其他shared_ptr对象共享指针。通俗一点解释,就是,f2和f3两个shared_ptr拥有两个控制块,且这两个...
  • 乐观是指操作数据库时(更新操作),想法很乐观,认为这次的操作不会导致冲突,在操作数据时,并不进行任何其他的特殊处理(也就是不加锁),而在进行更新后,再去判断是否有冲突了。 通常实现是这样的:在表中的...
  • 共享(S):多个事务可封锁一个共享页;任何事务都不能修改该页; 通常是该页被读取完毕,S立即被释放。 排它(X):仅允许一个事务封锁此页;其他任何事务必须等到X被释放才能对该页进行访问;X一直到事务...
  • 共享锁(S) 又称为读,可以查看但无法修改和删除的一种数据。如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排它。获准共享锁的事务只能读数据,不能修改数据。 共享锁下其它用户...
  • 一.S和X:S和X都属于行数 ...1)意向共享锁(IS):事务在请求S前,要先获得IS 2)意向排他(IX):事务在请求X前,要先获得IX 3、例子:事务A修改user表的记录r,会给记录r上一把行级的排...
  • 币BOSS共享型数字资产交易平台 未来世界去中心化社区自治型组织  《白皮书》 背景: 自比特币诞生以来,以区块链技术为基础的数字资产蓬勃发展,如今,数字资产的种类和影响力与日俱增。数字资产的公允价格形成...
  • 文件共享锁溢出 请增加MaxLocksperFile注册表项值 原因: Access数据库,同时操作大量记录(9500条以上)时报错。 Microsoft JET Database Engine 错误 '80040e21' 处理办法1:在注册表修改MaxLocksP...
  • 共享锁S:(读取)操作创建的。其他用户可以并发读取数据,但任何事物都不能获取数据上的排它,直到已释放所有共享锁。 SQL语法: SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE 排它X:排它又称为...
  • 等等,该包下很多的工具类都直接或者间接基于AQS提供的独占共享锁和等待队列实现了各自的同步需求。 一、AQS的设计:①AQS将资源抽象成一个int的值: int state ,通过对state的修改,达到对AQS不同状态的...
  • 更新的意思是:“我现在只想读,你们别人也可以读,但我将来可能会做更新操作,我已经获取了从共享锁(用来读)到排他  (用来更新)的资格”。一个事物只能有一个更新获此资格。    T1执行select,加更新...
  • 面试必备之乐观与悲观

    万次阅读 多人点赞 2018-07-16 22:34:26
    乐观对应于生活中乐观的人总是想着事情往好的方向发展,悲观对应于生活中悲观的人总是想着事情往坏的方向发展。这两种人各有优缺点,不能不以场景而定说一种人好于另外一种人。 悲观 总是假设最坏的情况...
  • 分布式是控制分布式系统之间同步访问共享资源的一种方式。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要通过一些互斥手段来防止彼此之间的干...
  • 共享锁是线程共享的,同一时刻能有多个线程拥有共享锁,但AQS里并没有用来存储获得共享锁的多个线程的成员。 如果一个线程刚获取了共享锁,那么在其之后等待的线程也很有可能能够获取到。但独占不会这样做,因为...
  • 共享单车上的智能车锁有何区别

    千次阅读 2017-07-05 14:08:50
    OFO小黄车6月21日上线NB-IOT物联网智能,引领共享单车新潮流。工信部发文明确推进NB-IOT建设,我们认为物联网发展进入新阶段,窄带物联产业链发展日趋完善,相关芯片模组厂商受益。NB-IOT小黄车上线,引领物联网新...
  • 首先,根据类型划分有排他(exclusive)共享(shared)。 下面举例: 通过DML语句对一张表的某一行数据进行修改,一个事务开始,背后的步骤是: 1.对这张表加一个共享锁。这么做是为了
  • UcosII 共享资源的机制的处理

    千次阅读 2017-08-31 11:15:41
    若在使用时,如对全局变量的同时的读写操作,可能会造成程序的崩溃,故在使用全局变量时,要对其进行或互斥处理。另,如果函数可以进行修改,则可通过函数的可重入性,即传形参使用局部变量,而避免使用全局变量。...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 103,590
精华内容 41,436
关键字:

共享型锁