天天看点

java mysql 事务锁_数据库事务&&Java锁相关整理

数据库事务:说一下事务的一些东西?你对事务的了解有哪些?说说数据库的乐观锁和悲观锁?

InnoDB支持事务,MyISAM不支持事务

四种属性

原子性、一致性、隔离性、持久性

原子性:一个事务要么成功提交,要么不成功提交。基于undo log和回滚操作实现。

一致性:

隔离性:一个事务的操作过程中不能被其他事务感知

持久性:事务的修改具有持久性

四种属性的实现:

原子性:基于undo log和回滚操作实现

一致性:其他三种属性保证一致性

持久性:redo log,每执行一条事务中的sql就记录在redo log上,事务一旦被提交redo log就将操作持久化到磁盘上。

隔离性:

名词解释:

读未提交:允许读取未提交的数据

读已提交:只允许读取已提交的数据

可重复读:一个事务两次读取同一数据,读到的结果是一样的

串行化:强制事务串行执行

脏读:事务A修改的数据,但未提交;这时事务B能够读取到A未提交的修改。

不可重复读:事务A两次读取的结果count没变,但是内容被不同。

幻读:事务A两次读取的结果count改变。

隔离性的实现方式一是加锁,

一是MVCC:https://juejin.im/post/5c519bb8f265da617831cfff

Java中有哪些锁:

为了方便记忆,将Java中线程与资源相互间的关系进行分类,分为三种类型:线程自身、线程与其他线程、线程与资源。具体如下图所示:

java mysql 事务锁_数据库事务&&Java锁相关整理

关于锁的详细讲解,可参考下面你这篇文章:https://tech.meituan.com/2018/11/15/java-lock.html

1. 悲观锁和乐观锁

悲观锁:基于认为自己在使用临界数据的时候,一定会有别的线程来竞争,因此在悲观锁模式下,线程在获取数据前先加锁,确保数据不会被竞争线程修改。Java中,sychronized关键字和Lock的实现类都是悲观锁。

乐观锁:基于认为自己在使用临界数据的时候,不会有别的线程来竞争,因此在乐观锁模式下,线程访问数据时不会加锁。只是在更新数据的时候,去判断之前有没有别的线程更新了这个数据,如果没被更新则正常执行;如果被更新了,则根据不同的方式执行不同的操作,如报错或自动重试。乐观锁在java中基于无锁编程实现,通常使用的是CAS算法。

java mysql 事务锁_数据库事务&&Java锁相关整理

悲观锁适合写操作多的场景,先加锁可以保证写操作时数据正确。乐观锁适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。2.共享锁/排他锁

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

共享锁是指该锁可被多个线程所持有。如果线程T对数据A加上共享锁后,则其他线程只能对A再加共享锁,不能加排它锁。获得共享锁的线程只能读数据,不能修改数据。

3. 自旋锁和自适应锁

线程阻塞唤醒使用的资源有时会大于线程占用CPU继续执行使用的资源,在这种情况下,一般会采用让线程自旋的方式,来等待锁释放,这就是自旋锁。

如果锁被占用的时间很短,自旋等待的效果就会非常好。反之,如果锁被占用的时间很长,那么自旋的线程只会白浪费处理器资源。为了更好的控制锁的自旋次数,引用了自适应锁。

自适应意味着自旋的次数由上一个线程在同一个锁上的自旋时间,以及锁的拥有者的状态来决定。如果在同一个锁对象上,自旋等待刚刚成功获得过锁,并且持有锁的线程正在运行中,那么虚拟机就会认为这次自旋很有可能再次成功,进而它将允许自旋等待持续相对更长的时间。如果对于某个锁,自旋很少成功获得过,那在以后尝试获取这个锁时将可能省略掉自旋过程,直接阻塞线程,避免浪费处理器资源。

4. 可重入锁/非可重入锁

可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),不会因为之前已经获取过还没释放而阻塞。Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。

通过重入锁ReentrantLock以及非可重入锁NonReentrantLock的源码来对比分析一下为什么非可重入锁在重复调用同步资源时会出现死锁。

首先ReentrantLock和NonReentrantLock都继承父类AQS,其父类AQS中维护了一个同步状态status来计数重入次数,status初始值为0。

当线程尝试获取锁时,可重入锁先尝试获取并更新status值,如果status == 0表示没有其他线程在执行同步代码,则把status置为1,当前线程开始执行。如果status != 0,则判断当前线程是否是获取到这个锁的线程,如果是的话执行status+1,且当前线程可以再次获取锁。而非可重入锁是直接去获取并尝试更新当前status的值,如果status != 0的话会导致其获取锁失败,当前线程阻塞。

释放锁时,可重入锁同样先获取当前status的值,在当前线程是持有锁的线程的前提下。如果status-1 == 0,则表示当前线程所有重复获取锁的操作都已经执行完毕,然后该线程才会真正释放锁。而非可重入锁则是在确定当前线程是持有锁的线程之后,直接将status置为0,将锁释放。

java mysql 事务锁_数据库事务&&Java锁相关整理

5. 无锁/偏向锁/轻量级锁/重量级锁

四种锁是指锁的状态,专门针对synchronized的。

无锁:没有对资源进行锁定,可以由多个线程修改资源,但同时只有一个能修改成功。无锁的特点就是修改操作在循环内进行,线程会不断的尝试修改共享资源,CAS就是无锁的体现。

偏向锁:当一段代码总是被某个线程访问时(可以简单理解为系统中只有这一个线程),该线程将会自动获取锁,降低获取锁的代价。当一个线程访问同步代码块并获取锁时,会在Mark Word里存储锁偏向的线程ID,在线程进入和退出同步块时不再通过CAS操作来加锁和解锁,而是检测Mark Word里是否存储着指向当前线程的偏向锁。线程不会主动释放偏向锁,偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态,撤销偏向锁后恢复到无锁(标志位为“01”)或轻量级锁(标志位为“00”)的状态。

轻量级锁:当锁是偏向锁的时候,另外的线程访问该锁,偏向锁就会升级为轻量级锁;另外的线程会通过自旋的方式等待,不会阻塞,提高性能。

重量级锁:当等待线程的自旋超过一定次数,或一个持有锁,一个自选等待,又有第三个线程来访时轻量级锁会升级为重量级锁,此时等待锁的线程都会进入阻塞状态。

综上:偏向锁通过对比Mark Word解决加锁问题,避免执行CAS操作。而轻量级锁是通过用CAS操作和自旋来解决加锁问题,避免线程阻塞和唤醒而影响性能。重量级锁是将除了拥有锁的线程以外的线程都阻塞。

6. 公平锁/非公平锁

公平锁是多个线程按照申请锁的先后顺序获取锁。公平锁的优点是等待锁的线程不会饿死。缺点是整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞,CPU唤醒阻塞线程的开销比非公平锁大。

非公平锁是线程执行到临界代码块时直接获取锁,获取不到才排在等待队列的队尾;如果获取到则直接执行。非公平锁的优点是可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程。缺点是处于等待队列中的线程可能会饿死,或者等很久才会获得锁。

ReentrantLock里面有一个内部类Sync,Sync继承AQS(AbstractQueuedSynchronizer),添加锁和释放锁的大部分操作实际上都是在Sync中实现的。它有公平锁FairSync和非公平锁NonfairSync两个子类。ReentrantLock默认使用非公平锁,也可以通过构造器来显示的指定使用公平锁。下面我们来看一下公平锁与非公平锁的加锁方法的源码:

java mysql 事务锁_数据库事务&&Java锁相关整理

通过上图中的源代码对比,我们可以明显的看出公平锁与非公平锁的lock()方法唯一的区别就在于公平锁在获取同步状态时多了一个限制条件:hasQueuedPredecessors()。

java mysql 事务锁_数据库事务&&Java锁相关整理

在hasQueuedPredecessors()方法中判断当前线程是否位于同步队列的第一个,如果是返回true,否则返回false。

更加详细的信息,请阅读:https://tech.meituan.com/2018/11/15/java-lock.html