精华内容
下载资源
问答
  • 本文实例讲述了mysql共享锁与排他锁用法。分享给大家供大家参考,具体如下: mysql锁机制分为表级锁和行级锁,本文就和大家分享一下我对mysql中行级锁中的共享锁与排他锁进行分享交流。 共享锁又称为读锁,简称S锁,...
  • 重要声明:本人之前对java中的读写锁也不是非常了解,用的也不是很多,尤其在读写锁的策略原理一块没有深究过,本篇文章是在学习【玩...在【ReentrantLock锁详解】一文中讲到了java中锁的划分,本篇主要讲述共享锁和...

    重要声明:本人之前对java中的读写锁也不是非常了解,用的也不是很多,尤其在读写锁的策略原理一块没有深究过,本篇文章是在学习【玩转Java并发工具,精通JUC,成为并发多面手】课程后写的,故文章类型选择为"转载",因为本文的很多结论都是来自于那门课程,请知悉~。希望对各位同仁有帮助~


    books 读写锁的基本使用

    在【ReentrantLock锁详解】一文中讲到了java中锁的划分,本篇主要讲述共享锁和排他锁:ReentrantReadWriteLock

    在ReentrantReadWriteLock中包含读锁和写锁,其中读锁是可以多线程共享的,即共享锁,而写锁是排他锁,在更改时候不允许其他线程操作。读写锁其实是一把锁,所以会有同一时刻不允许读写锁共存的规定。之所以要细分读锁和写锁也是为了提高效率,将读和写分离,对比ReentrantLock就可以发现,无论并发读还是写,它总会先锁住全部再说。

    接着先来个代码演示下,读锁是共享锁,写锁是排他锁:

    /**
     * ReentrantReadWriteLock读写锁示例
     **/
    public class ReentrantReadWriteLockTest {
    
        private static ReentrantReadWriteLock reentrantLock = new ReentrantReadWriteLock();
        private static ReentrantReadWriteLock.ReadLock readLock = reentrantLock.readLock();
        private static ReentrantReadWriteLock.WriteLock writeLock = reentrantLock.writeLock();
    
        public static void read() {
            readLock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "获取读锁,开始执行");
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                readLock.unlock();
                System.out.println(Thread.currentThread().getName() + "释放读锁");
            }
        }
    
        public static void write() {
            writeLock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "获取写锁,开始执行");
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                writeLock.unlock();
                System.out.println(Thread.currentThread().getName() + "释放写锁");
            }
        }
    
        public static void main(String[] args) {
            new Thread(() -> read(), "Thread1").start();
            new Thread(() -> read(), "Thread2").start();
            new Thread(() -> write(), "Thread3").start();
            new Thread(() -> write(), "Thread4").start();
        }
    }

    输出结果如下,线程1和线程2可以同时获取读锁,而线程3和线程4只能依次获取写锁,因为线程4必须等待线程3释放写锁后才能获取到锁:

    Thread1获取读锁,开始执行
    Thread2获取读锁,开始执行
    Thread1释放读锁
    Thread2释放读锁
    Thread3获取写锁,开始执行
    Thread3释放写锁
    Thread4获取写锁,开始执行
    Thread4释放写锁

    books 读锁的插队策略

    设想如下场景:在非公平的ReentrantReadWriteLock锁中,线程2和线程4正在同时读取,线程3想要写入,拿不到锁(同一时刻是不允许读写锁共存的),于是进入等待队列,线程5不在队列里,现在过来想要读取策略1是如果允许读插队,就是说线程5读先于线程3写操作执行,因为读锁是共享锁,不影响后面的线程3的写操作,这种策略可以提高一定的效率,却可能导致像线程3这样的线程一直在等待中,因为可能线程5读操作之后又来了n个线程也进行读操作,造成线程饥饿策略2是不允许插队,即线程5的读操作必须排在线程3的写操作之后,放入队列中,排在线程3之后,这样能避免线程饥饿

    事实上ReentrantReadWriteLock在非公平情况下,读锁采用的就是策略2:不允许读锁插队,避免线程饥饿。更加确切的说是:在非公平锁情况下,允许写锁插队,也允许读锁插队,但是读锁插队的前提是队列中的头节点不能是想获取写锁的线程。

    以上还在非公平ReentrantReadWriteLock锁中,在公平锁中,读写锁都是是不允许插队的,严格按照线程请求获取锁顺序执行。

    下面用代码演示一下结论:在非公平锁情况下,允许写锁插队,也允许读锁插队,但是读锁插队的前提是队列中的头节点不能是想获取写锁的线程

    /**
     * ReentrantReadWriteLock读写锁插队策略测试
     **/
    public class ReentrantReadWriteLockTest {
    
        private static ReentrantReadWriteLock reentrantLock = new ReentrantReadWriteLock();
        private static ReentrantReadWriteLock.ReadLock readLock = reentrantLock.readLock();
        private static ReentrantReadWriteLock.WriteLock writeLock = reentrantLock.writeLock();
    
        public static void read() {
            System.out.println(Thread.currentThread().getName() + "开始尝试获取读锁");
            readLock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "获取读锁,开始执行");
                Thread.sleep(20);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                readLock.unlock();
                System.out.println(Thread.currentThread().getName() + "释放读锁");
            }
        }
    
        public static void write() {
            System.out.println(Thread.currentThread().getName() + "开始尝试获取写锁");
            writeLock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "获取写锁,开始执行");
                Thread.sleep(40);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName() + "释放写锁");
                writeLock.unlock();
            }
        }
    
        public static void main(String[] args) {
            new Thread(() -> write(), "Thread1").start();
            new Thread(() -> read(), "Thread2").start();
            new Thread(() -> read(), "Thread3").start();
            new Thread(() -> write(), "Thread4").start();
            new Thread(() -> read(), "Thread5").start();
            new Thread(() -> {
                Thread[] threads = new Thread[1000];
                for (int i = 0; i < 1000; i++) {
                    threads[i] = new Thread(() -> read(), "子线程创建的Thread" + i);
                }
                for (int i = 0; i < 1000; i++) {
                    threads[i].start();
                }
            }).start();
        }
    }

    以上测试代码就演示了,在非公平锁时,其一:同一时刻读写锁不能同时存在, 其二,读锁非常容易插队,但前提是队列中的头结点不能是想获取写锁的线程。

    books 锁的升降级

    升降级是指读锁升级为写锁,写锁降级为度锁。在ReentrantReadWriteLock读写锁中,只支持写锁降级为读锁,而不支持读锁升级为写锁,如下代码测试所示:

    /**
     * ReentrantReadWriteLock锁升降级测试
     **/
    public class ReentrantReadWriteLockTest {
    
        private static ReentrantReadWriteLock reentrantLock = new ReentrantReadWriteLock();
        private static ReentrantReadWriteLock.ReadLock readLock = reentrantLock.readLock();
        private static ReentrantReadWriteLock.WriteLock writeLock = reentrantLock.writeLock();
    
        public static void read() {
            System.out.println(Thread.currentThread().getName() + "开始尝试获取读锁");
            readLock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "获取读锁,开始执行");
                Thread.sleep(20);
                System.out.println(Thread.currentThread().getName()+ "尝试升级读锁为写锁");
                //读锁升级为写锁(失败)
                writeLock.lock();
                System.out.println(Thread.currentThread().getName() +"读锁升级为写锁成功");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                readLock.unlock();
                System.out.println(Thread.currentThread().getName() + "释放读锁");
            }
        }
    
        public static void write() {
            System.out.println(Thread.currentThread().getName() + "开始尝试获取写锁");
            writeLock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + "获取写锁,开始执行");
                Thread.sleep(40);
                System.out.println(Thread.currentThread().getName() +"尝试降级写锁为读锁");
                //写锁降级为读锁(成功)
                readLock.lock();
                System.out.println(Thread.currentThread().getName()+ "写锁降级为读锁成功");
                System.out.println();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName() + "释放写锁");
                writeLock.unlock();
                readLock.unlock();
            }
        }
    
        public static void main(String[] args) {
            new Thread(() -> write(), "Thread1").start();
            new Thread(() -> read(), "Thread2").start();
        }
    }

    运行控制台输出:

    Thread1开始尝试获取写锁
    Thread1获取写锁,开始执行
    Thread1尝试降级写锁为读锁
    Thread1写锁降级为读锁成功
    Thread1释放写锁
    
    Thread2开始尝试获取读锁
    Thread2获取读锁,开始执行
    Thread2尝试升级读锁为写锁

    之所以ReentrantReadWriteLock不支持锁的升级(其它锁可以支持),主要是避免死锁,例如两个线程A和B都在读, A升级要求B释放读锁,B升级要求A释放读锁,互相等待形成死循环。如果能严格保证每次都只有一个线程升级那也是可以的。

    books 总结

    1. 读写锁特点特点:读锁是共享锁,写锁是排他锁,读锁和写锁不能同时存在
    2. 插队策略:为了防止线程饥饿,读锁不能插队
    3. 升级策略:只能降级,不能升级
    4. ReentrantReadWriteLock适合于读多写少的场合,可以提高并发效率,而ReentrantLock适合普通场合

    books 引申阅读:

    展开全文
  • 锁有两种分类方法。(1) 从数据库系统的角度来看锁分为以下三种类型: •独占锁(Exclusive Lock)独占锁锁定的资源只允许进行锁定操作的程序使用,其它任何对...通常加共享锁的数据页被读取完毕后,共享锁就会立即被
  • 独占锁与共享锁

    千次阅读 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”。所以读写锁才能实现读读的过程共享,而读写、写读、写写的过程互斥。

    展开全文
  • 目录 背景 独占锁与共享锁 独占锁 共享锁 公平锁与非公平锁 公平锁 非公平锁 可重入锁 总结 背景 最近有一些小伙伴会问我一些关于并发相关的问题,在与他们的沟通中,我发现他们对锁的概念很模糊。这部分基础概念的...

    目录

    背景

    独占锁与共享锁

    独占锁

    共享锁

    公平锁与非公平锁

    公平锁

    非公平锁

    可重入锁

    总结


    背景

    最近有一些小伙伴会问我一些关于并发相关的问题,在与他们的沟通中,我发现他们对锁的概念很模糊。这部分基础概念的缺失导致他们写的程序经常死锁,还无法bebug。虽然在网上有很多资料,但我还是用一些比较通俗易懂的例子,描述一下,帮助大家理解。

    独占锁与共享锁

    我发现很多人会把独占锁与公平锁搞混,其实他们不在一个方向上。

    独占锁

    独占锁是指在同一个时间只能有一个线程占有他,所以具备排他性。像synchronized关键字就是典型的独占锁。

    举个栗子:独占锁相当于独间的澡堂,一个人进了这件澡堂,其他人都不能进,只有等他出来。

    共享锁

    什么是共享锁?

    就是指同一时间可以被多个线程占有,像java自带的ReadWriteLock、Semaphore,他们可以设置自己共享的数量。

    举个栗子:共享锁相当于一个大型公共澡堂,一开始就设定了能进去洗澡人的数量,比如是10个人,那么你要进去的时候会检查一下有没有满10个,满了你就在门口排队去吧。

    公平锁与非公平锁

    如果你在排队买奶茶,有个人插队了但是没人制止,你生不生气?他买到了奶茶扬长而去,这公不公平。

    公平锁

    公平锁就是多个线程去申请锁的使用权的时候,线程会直接进入队列排队,排在前面的可以先获得锁,排在后面的只能等着前面的先用。

    举个栗子:有个澡堂,里面已经满了,后面的人想冲进去。但是有个保安站在门口,他让后面想进来的人都排好队,出来一个,就从队伍的前面放一个人进去。

    没错,这就很公平。

    但是公平锁也会带来其他的缺点,就是需要一个保安去控制,带来的其他开销。如果大家蜂拥而至去抢位置,少了这个保安,会更有效率。没错公平锁的缺点就是会带来更大的开销以及吞吐量下降。

    非公平锁

    与公平锁对应的就是非公平了,简而言之就是和公平锁反正来,什么不公平来什么。关键字synchronized就是典型的非公平锁。

    缺点就是不公平,在某些场景中,特别是每次获取锁后会迅速执行并且释放锁的情况下,非公平锁是可以使用的。假设你洗澡的时间巨长,还有人插我队,这谁能忍得了?

    可重入锁

    可重入锁的概念很多人容易理解错,他是指同一个线程在申请到锁的情况下,继续申请锁不会阻塞,而是有个计数器记录该线程以及线程申请的次数。

    举个栗子:还是单间浴室,但是我有一个洗澡卡,一旦我进了洗澡间就要刷卡,而且出来的时候还需要刷卡退出。那么我一旦进了洗澡间,我可以刷好几次进入卡,这不会阻塞住,因为都是我自己刷的。但是我退出洗澡间的时候我得刷同样次数的退出卡,不然下一个进来的人刷不了进入卡。好理解吗?你也别管我刷几次,反正我刷几次进入,就刷几次退出。

    像java的关键字synchronized和ReentrantLock类都是可重入锁。

    总结

    希望这些简单的栗子可以帮主你理解java一些锁知识,不要把生硬的文字过度理解,跳出文字本身,设想一些生活中的场景,可以帮助我们更好的理解知识。

    分享:

            知识有两种,其一是我们自己精通的问题;其二是我们知道在哪里找到关于某问题的知识。                                                                                        ——约翰生

    如果本文对你有用的话,请点个赞吧,谢谢!

    展开全文
  • 又称读锁(S锁),共享锁不阻塞其他事务的读操作,但阻塞写操作,同一数据对象A可以共存多个共享锁,这被称为共享锁兼容。 当T1为数据对象A加上共享锁后,可以对A进行读操作,但不能进行写操作,并且T2可以再次对A加...

    一,锁的种类

    1.共享锁——Shared lock
    又称读锁(S锁),共享锁不阻塞其他事务的读操作,但阻塞写操作,同一数据对象A可以共存多个共享锁,这被称为共享锁兼容。

    当T1为数据对象A加上共享锁后,可以对A进行读操作,但不能进行写操作,并且T2可以再次对A加共享锁,大家都可以正常地读A,但是在A上的共享锁释放之前,任何事务不可以对A进行写操作。

    例1:

    T1:select * from table

    T2:update table set column1=‘hello’

    分析:假设T1先执行,则T2必须等待T1执行完才可以执行。因为T2为写操作,需要为table加一个排他锁,而数据库规定相同资源不可以同时存在共享锁和排他锁,所以T2必须等待T1执行完,释放掉共享锁,才可以加排他锁,然后执行update。

    例2:(死锁的发生)

    T1:

    begin Transaction t1

    select * from table with (holdlock) (holdlock的意思是加共享锁,直到事务结束(提交或回滚)才会释放)

    update table set column1=‘hello’

    T2:

    begin Transaction t2

    select * from table with (holdlock)

    update table set column1=‘world’

    分析:假设T1和T2同时到达select语句,都为table加上了共享锁,那么当T1、T2要执行update时,根据锁机制,共享锁需要升级为排他锁,但是排他锁与共享锁不能共存,要给table加排他锁,必须等待table上的共享锁全部释放才可以,可是holdlock的共享锁必须等待事务结束才能释放,因此T1和T2都在等待对方释放共享锁,形成循环等待,造成死锁。

    例3:

    T1:update table set column1=‘hello’ where id=‘001’

    T2:update table set column1=‘world’ where id=‘002’

    分析:此种情况有可能造成等待,分为id列有索引与无索引两种情况。

    (1)id列有索引,则T1直接定位到id='001’行,加排他锁,更新;T2直接定位到id='002’行,加排他锁,更新。互不影响。

    (2)id列无索引,T1扫描全表,找到id='001’行,加排他锁后,T2为了找到id='002’行,需要全表扫描,那么就会为table加共享锁或更新锁或排他锁,但不管加什么锁,都需要等待T1释放id='001’行的排他锁,不然无法为全表加锁。

    死锁可以通过直接对表加排他锁来解决,即将事务的隔离级别提高至最高级——串行读,各个事务串行执行,可是这样虽然避免了死锁,但是效率太低了,那我们干脆别发明并发这个词语好了。

    2.更新锁——Update lock
    更新锁(U锁)。当T1给资源A加上更新锁后,代表该资源将在稍后更新,更新锁与共享锁兼容,更新锁可以防止例2里那种一般情况的死锁发生,更新锁会阻塞其他的更新锁和排他锁。因此相同资源上不能存在多个更新锁。

    更新锁允许其他事务在更新之前读取资源。但不可以修改。因为其他事务想获取资源的排他锁时,发现该资源已存在U锁,则等待U锁释放。

    在T1找到需要更新的数据时,更新锁直接转为排他锁,开始更新数据,不需要等待其他事务释放共享锁啥的。

    那么就问了,共享锁为什么不可以直接升级为排他锁,而必须等待其他共享锁都释放掉才可以转为排他锁呢?

    这就是共享锁和更新锁的一个区别了,共享锁之间是兼容的,但是更新锁之间互不兼容,因此仅有一个更新锁直接转为排他锁是安全的,而多个共享锁问也不问直接转为排他锁,那怎么行呢,排他锁只能有一个的,这就是为什么共享锁需要等待其他共享锁释放才可以升级为排他锁的原因了。

    例4:

    T1:

    begin

    select * from table with (updlock) (加更新锁)

    update table set column1=‘hello’ (重点:这里T1做update时,不需要等T2释放什么,而是直接把更新锁升级为排他锁,然后执行update)

    T2:

    begin

    select * from table (T1的更新锁不影响T2的select)

    update table set column1=‘world’ (T2的update需要等待T1的update执行完)

    分析:(1)T1先到达,T1的select句对table加更新锁,此时T2紧接着到达,T2的select句对table加共享锁,假设T2的select先执行完,要开始T2的update,发现table已有更新锁,则T2等,T1此时执行完select,然后将更新锁升级为排他锁,开始更新数据,执行完成,事务结束,释放排他锁,此时T2才开始对table加排他锁并更新。

    (2)T2先到,T1紧接着,T2加共享锁 => T1加更新锁 => 假设T2先结束select => 试图将共享锁升级为排他锁 => 发现已有更新锁 => 之后的情况同

    3.排他锁——Exclusive Locks
    又叫独占锁,写锁,X锁,很容易理解,排他锁阻塞任何锁,假设T1为资源A加上排他锁,则其他事务不允许对资源A进行任何的读写操作。

    例5:(假设id都是自增长且连续的)

    T1: update table set column1=‘hello’ where id<1000

    T2: update table set column1=‘world’ where id>1000

    假设T1先达,T2随后至,这个过程中T1会对id<1000的记录施加排他锁.但不会阻塞T2的update。

    例6:

    T1: update table set column1=‘hello’ where id<1000

    T2: update table set column1=‘world’ where id>900

    假设T1先达,T2立刻也到,T1加的排他锁会阻塞T2的update。

    4.意向锁——Intent Locks
    意向锁,就是说当你给数据加锁时,必须先给他的上级加锁,用来向其他事务表明这段数据中的某些数据正在被加某某锁,你看着办吧。其实是一个节省开销的做法。

    例7:

    T1:

    begin tran

    select * from table with (xlock) where id=10 --意思是对id=10这一行强加排他锁

    T2:

    begin tran

    select * from table with (tablock) --意思是要加表级锁

    假设T1先执行,T2后执行,T2执行时,欲加表锁,为判断是否可以加表锁,数据库系统要逐条判断table表每行记录是否已有排他锁,

    如果发现其中一行已经有排他锁了,就不允许再加表锁了。只是这样逐条判断效率太低了。

    实际上,数据库系统不是这样工作的。当T1的select执行时,系统对表table的id=10的这一行加了排他锁,还同时悄悄的对整个表加了意向排他锁(IX),当T2执行表锁时,只需要看到这个表已经有意向排他锁存在,就直接等待,而不需要逐条检查资源了。

    常用的意向锁有三种:意向共享锁(Intent Share Lock,简称IS锁);意向排他锁(Intent Exclusive Lock,简称IX锁);共享意向排它锁(Share Intent Exclusive Lock,简称SIX锁),共享意向排它锁的意思是,某事务要读取整个表,并更新其中的某些数据。

    二、如何加锁

    1.数据库自动加锁
    其实锁在大多数情况下都是数据库自动加的,比如这么一条语句:

    update table set column1=‘hello’

    通过Profiler跟踪sql发现,他会逐行先获取U锁,然后转为X锁,更新完这一行,不释放X锁,继续获取下一行的U锁,转X…一直到全部更新结束,再逐行释放掉所有的X锁。

    而如果加上where条件,如:update table set column1=‘hello’ where column2=‘world’,并且column2无索引,则逐行获取U锁,如果符合条件,转X锁,更新,不释放X锁;如果不符合,释放U锁。

    但是如果有索引的话,则不需要逐行获取U锁 => 判断 => 转X锁或释放,而是直接获取到要更新行的X锁,更新,释放X锁即可。
    所有的U,X,S之前都会首先为表或者页加意向锁。

    2.手动加锁
    例8:

    T1:select * from table with (tablock) --对表加共享锁,且事务不完成,共享锁不释放。

    T2:select * from table with (holdlock) --对涉及范围内加共享锁,事务不完成,共享锁不释放。

    我感觉可能大多数情况下是不需要我们手动加锁的,因为我们不是专业搞数据库的,很多场景可能预想不到,就会导致一些错误,我们管理事务的隔离级别就可以了,数据库会根据隔离级别的不同,按照策略来加锁。

    三、锁的粒度

    锁的粒度指的是锁生效的范围,即行锁,页锁,或者是表锁。锁的粒度一般由数据库自主管理,不同的事物隔离级别,数据库会有不同的加锁策略(比如加什么类型的锁,加什么粒度的锁)。也可以手动指定。在下面的例子中,我们假设id是自增的主键。

    例9:

    T1::select * from table with (paglock) --页锁

    T2::update table set column1=‘hello’ where id>10

    T1执行时,会先对第一页加共享(S)锁,读完第一页后,释放锁,再对第二页加共享锁,依此类推。假设前10行记录恰好是一页(当然,一般不可能一页只有10行记录),那么T1执行到第一页查询时,并不会阻塞T2的更新。

    例10:

    T1:select * from table with (rowlock) --行锁

    T2:update table set column1=‘hello’ where id=10

    T1执行时,对每行加共享锁,读取,然后释放,再对下一行加锁;T2执行时,会对id=10的那一行试图加锁,只要该行没有被T1加上行锁,T2就可以顺利执行update操作。

    例11:

    T1:select * from table with (tablock) --表锁

    T2:update table set column1=‘hello’ where id = 10

    T1执行,对整个表加共享锁。T1必须完全查询完,T2才可以允许加锁,并开始更新。

    通过分析以上3个例子可以得出结论:锁的粒度与系统的并发性和系统开销密切相关。粒度越小,则并发度越大,开销越大;反之,粒度越大,则并发度越小,开销越小。

    四.锁与事务隔离级别的优先级

    先上结论,手工指定的锁优先。

    例12:

    T1:GO

    SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

    GO

    BEGIN TRANSACTION

    SELECT * FROM table with (NOLOCK)

    GO

    T2:update table set column1=‘hello’ where id=10

    T1是事物隔离级别为最高级,串行读,数据库系统本应对后面的select语句自动加表级锁,但因为手工指定了NOLOCK,所以该select语句不会加任何锁,所以T2也就不会有任何阻塞。

    五、数据库的几个 重要Hint及他们的区别

    1. holdlock 对表加共享锁,且事物不完成,共享锁不释放。

    2. tablock 对表加共享锁,只要statement不完成,共享锁不释放。

    3. TABLOCKX 对表加排他锁,事务不完成,排他锁不释放。

    4. xlock 加排他锁,和tablockx的区别:tablockx只能对整张表加锁,而xlock可以指定锁的粒度。

    例13:

    select * from table with (xlock paglock) --对page加排他锁

    select * from table with (xlock tablock) --效果等同于select * from table with (tablockx)

    六、如何提高并发效率

    1.悲观锁
    利用数据库本身的锁机制实现。通过上面对数据库锁的了解,可以根据具体业务情况综合使用事务隔离级别与合理的手工指定锁的方式比如降低锁的粒度等减少并发等待。

    2.乐观锁
    利用程序处理并发。原理都比较好理解,基本一看即懂。方式大概有以下3种:

    (1)对记录加版本号。
    
    (2)对记录加时间戳。
    
    (3)对将要更新的数据进行提前读取、事后对比。
    
    首先说明一点的是:乐观锁在数据库上的实现完全是逻辑的,数据库本身不提供支持,而是需要开发者自己来实现。
    

    常见的做法有两种:版本号控制及时间戳控制。

    版本号控制的原理:

    为表中加一个 version 字段;
    当读取数据时,连同这个 version 字段一起读出;
    数据每更新一次就将此值加一;
    当提交更新时,判断数据库表中对应记录的当前版本号是否与之前取出来的版本号一致,如果一致则可以直接更新,如果不一致则表示是过期数据需要重试或者做其它操作(PS:这完完全全就是 CAS 的实现逻辑呀~)
    至于时间戳控制,其原理和版本号控制差不多,也是在表中添加一个 timestamp 的时间戳字段,然后提交更新时判断数据库中对应记录的当前时间戳是否与之前取出来的时间戳一致,一致就更新,不一致就重试。
    在这里插入图片描述
    CAS是什么?
    CAS是英文单词CompareAndSwap的缩写,中文意思是:比较并替换。CAS需要有3个操作数:内存地址V,旧的预期值A,即将要更新的目标值B。

    CAS指令执行时,当且仅当内存地址V的值与预期值A相等时,将内存地址V的值修改为B,否则就什么都不做。整个比较并替换的操作是一个原子操作。

    CAS是乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。

    展开全文
  • Mysql共享锁和排他锁

    万次阅读 多人点赞 2019-05-27 15:10:41
    不知道图片能不能正常显示 mysql锁机制分为表级锁和行级锁,...共享锁又称为读锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。 排他锁又称为写锁...
  • 的概述一. 为什么要引入多个用户同时对数据库的并发操作时会带来以下数据不一致的问题:丢失更新A,B两个用户读同一数据并进行修改,其中一个用户的修改结果破坏了另一个修改的结果,比如订票系统脏读A用户修改了...
  • mysql共享锁 排他锁 意向锁

    千次阅读 2019-04-01 13:34:36
    mysql锁机制分为表锁和行锁,其中行锁又包括了共享锁与排他锁。 共享锁: 又称为读锁(S锁),当有多个事务时,多个事务对于同一数据可以共享一个锁,都能访问到数据,但是其他事务只能读不能写。 排他锁 又称为...
  • Mysql共享锁、排他锁、悲观锁、乐观锁及其使用场景 一、相关名词 |--表级锁(锁定整个表) |--页级锁(锁定一页) |--行级锁(锁定一行) |--共享锁(S锁,MyISAM 叫做读锁) |--排他锁(X锁,MyISAM 叫做写锁...
  • mysql共享锁和排它锁

    千次阅读 2019-04-09 14:43:14
    |--共享锁(S锁,MyISAM 叫做读锁) |--排他锁(X锁,MyISAM 叫做写锁) |--悲观锁(抽象性,不真实存在这个锁) |--乐观锁(抽象性,不真实存在这个锁)   二、InnoDB与MyISAM Mysql 在5.5之前默认使用 MyISAM...
  • MySQL:共享锁 互斥锁 意向锁。

    千次阅读 2019-12-16 14:01:43
    MySQL:共享锁 互斥锁 意向锁。
  • 共享锁是线程共享的,同一时刻能有多个线程拥有共享锁,但AQS里并没有用来存储获得共享锁的多个线程的成员。 如果一个线程刚获取了共享锁,那么在其之后等待的线程也很有可能能够获取到锁。但独占锁不会这样做,因为...
  • MySQL共享锁与排他锁

    千次阅读 2018-10-22 12:50:53
    mysql锁机制分为表级锁...共享锁又称为读锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。 排他锁又称为写锁,简称X锁,顾名思义,排他锁就是不能与其...
  • 共享锁和排它锁

    2020-06-04 23:42:40
    共享锁和排它锁 以 ReentranReadWriteLock 读写锁为例 什么是共享锁和排它锁 排它锁,又称独占锁,独享锁 synchronized就是一个排它锁 共享锁,又称为读锁,获得共享锁后,可以查看,但无法删除和修改数 ...
  • 上篇文章通过ReetrantLock分析了独占锁模式的实现原理,即基于AQS同步框架,本篇打算从Semaphore入手分析共享锁模式的实现原理,与独占锁模式不同的是,共享锁模式允许同一个时刻多个线程可获取同步状态。...
  • 分布式是控制分布式系统之间同步访问共享资源的一种方式。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要通过一些互斥手段来防止彼此之间的干...
  • 1.表级与行级 表级: table-level locking,住整个表。 开销小,加锁快。 不会死锁(一次性加载所需的所有表)。 粒度大,发生冲突概率大,并发效率低。 适合查询。 行级: row-level loking,住一...
  • 1、Java读写锁理论 ...对ReentrantReadWriteLock而言,其读锁是共享锁,其写锁是独占锁。读锁的共享性可保证并发读是非常高效的,读写、写读、写写的过程都是互斥的。 2、Java读写锁代码验证 Demo One:代...
  • 本文通过代码实操讲解了如何使用 python 实现简单的共享锁和排他锁。 上篇文章回顾:记一次容量提升5倍的HttpDns业务Cache调优 共享锁和排它锁 1、什么是共享锁 共享锁又称为读锁。 从多线程的角度来讲,共享...
  • mysql共享锁、排他锁、意向锁

    千次阅读 2019-07-23 15:21:45
    1.意向排他锁和共享锁与排他锁冲突 (锁住一行的写锁,其他事务不可获取该表任何锁) 2.意向共享锁共享锁兼容,和排他锁冲突 (锁住一行的读锁,另一事务可获取该表的读锁,不可获取该表的写锁) 3.意向锁之间相互...
  • 共享锁(读锁)、排他锁(写锁) 状态锁 意向共享锁、意向排他锁 一、乐观锁和悲观锁 1.乐观锁介绍 乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在...
  • 1. ReentrantReadWriteLock中PROPAGATE只是一个中间状态,共享锁的传播性由setHeadAndPropagate完成。 2. 对于有资源概念的Semaphore,PROPAGATE和setHeadAndPropagate组合完成共享锁的传播性。 3. 共享锁的传播性...
  • 排他锁和共享锁

    千次阅读 2018-10-11 22:13:55
     当多事务争取一个资源时,有可能导致数据不一致,这个时候需要一种机制限制,并且将数据访问顺序化,用来保证数据库数据的一致性,就是其中的一种机制。我们可以用商场的试衣间来做个比喻,商场里得每个试衣间都...
  • 乐观是指操作数据库时(更新操作),想法很乐观,认为这次的操作不会导致冲突,在操作数据时,并不进行任何其他的特殊处理(也就是不加锁),而在进行更新后,再去判断是否有冲突了。 通常实现是这样的:在表中的...
  • AQS源码分析之独占锁和共享锁

    千次阅读 2017-07-13 15:06:45
    AQS实现机制并不是通过synchronized——给对象加锁实现的,事实上它仅仅是一个工具类!它没有使用更高级的机器指令,也不靠关键字,更不依靠JDK编译时的特殊处理,仅仅作为一个普普通通的类就完成了代码块的访问...
  • Semaphore共享锁的使用 信号量(Semaphore),又被称为信号灯,在多线程环境下用于协调各个线程, 以保证它们能够正确、合理的使用公共资源。信号量维护了一个许可集,我们在初始化Semaphore时需要为这个许可集传入一个...
  • java并发-独占锁与共享锁

    千次阅读 2019-06-28 09:21:11
    java并发包提供的加锁模式分为独占锁和共享锁,独占锁模式下,每次只能有一个线程能持有锁,ReentrantLock就是以独占方式实现的互斥锁。共享锁,则允许多个线程同时获取锁,并发访问 共享...
  • mysql 共享锁和排他锁的概念

    千次阅读 2019-03-12 22:42:23
    1、共享锁 又称读锁 是读取操作创建的锁。其他用户可以并发读取数据,但任何事务都不能对数据进行修改(获取数据上的排他锁),直到已释放所有共享锁。 在查询语句后面增加LOCK IN SHARE MODE,MySQL 就会对查询结果...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 421,592
精华内容 168,636
关键字:

共享锁

友情链接: 类的成员.zip