Java锁

Java中锁

Java中的锁是用于控制对共享资源的访问的机制。它们是多线程编程中的关键组件,用于确保在并发环境中数据的一致性和线程安全性。
使用锁的目的
多个外部线程同时来竞争使用同一资源时,会彼此影响,导致混乱
锁的目的,将资源的使用做排它性处理,使同一时间,仅一个线程能访问资源
并不是所有的资源,都无法同时服务多个线程 ------ 比如,无状态的资源
无成员变量/成员变量不存在变化的类---- 就是无状态类 ----- 这种类是线程安全的
有状态的对象,也不一定是不安全的
如果状态变化是原子的(即没有中间变迁过程,变化不需要时间,没有中间态) ---- 那么它一样是线程安全的
动作的原子性
总结:锁的本质
锁要解决的问题是 ------- 资源数据会不一致
锁要达成的目标是 ------- 让资源使用起来,像原子性一样
锁达成目标的手段 ------- 让使用者访问资源时,只能排队,一个一个地去访问资源

Java中的锁有几种类型

synchronized关键字
synchronized关键字可以应用于方法或代码块,它在Java中是最基本的锁机制。当一个线程进入synchronized方法或代码块时,它会尝试获得锁,并在执行完毕后释放锁。其他线程必须等待直到锁被释放。

public synchronized void synchronizedMethod() {
    // synchronized方法体
}

ReentrantLock
ReentrantLock是java.util.concurrent包中提供的一种锁机制。它提供了比synchronized更多的灵活性和功能,如可中断锁、可超时锁、公平性等。使用ReentrantLock时,需要手动地获取锁和释放锁。

import java.util.concurrent.locks.ReentrantLock;

ReentrantLock lock = new ReentrantLock();

lock.lock(); // 获取锁
try {
    // 临界区代码
} finally {
    lock.unlock(); // 释放锁
}
  1. ReadWriteLock:ReadWriteLock接口支持读写分离锁定机制,允许多个线程同时读取共享资源,但在写入时必须独占锁。ReadWriteLock包括读锁和写锁。
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

ReadWriteLock rwLock = new ReentrantReadWriteLock();

rwLock.readLock().lock(); // 获取读锁
try {
    // 读操作
} finally {
    rwLock.readLock().unlock(); // 释放读锁
}

rwLock.writeLock().lock(); // 获取写锁
try {
    // 写操作
} finally {
    rwLock.writeLock().unlock(); // 释放写锁
}

这些是Java中常见的锁机制。选择哪种锁取决于你的具体需求,例如,如果你需要更多的控制和功能,可以选择ReentrantLock,如果你只需要简单的锁机制,synchronized可能就足够了。

Java 锁的分类

  1. 思想上的锁: 悲观锁、乐观锁
  2. 公平锁与非公平锁
  3. 自旋锁/重入锁
  4. 重量级锁与轻量级锁
  5. 独占锁与共享锁

锁优化

减少锁持有时间、
减小锁粒度–ConcurrentHashMap
锁分离-多写分离锁代替独占锁 读写锁 ReadWriteLock
锁粗化
锁消除
在并发场景中,我们的代码中经常会用到锁。存在锁,就必然存在锁的竞争,存在锁的竞争,就会消耗很多资源。
那么,如何优化我们ava代码中的锁呢?主要可以从以下几个方面考虑:
减少锁持有时间
可以使用同步代码块来代替同步方法。这样可以减少锁持有的时间。
只用在有线程安全要求的程序上加锁
减少锁粒度
要在并发场景中使用map的时候,记得使用concurenthashmap来代替hashtable和hashmap.
(chm采用分段锁,锁的粒度会更小)
将大对象(这个对象可能会被很多线程访问),拆成小对象,大大增加并行度,降低锁竞争。降低了锁的竞争,偏向锁,轻量级锁成功率才会提高。最最典型的减小锁粒度的案例就是ConcurrentHashMap
锁分离
普通锁(如syncronized)会导致读阻塞写,写也会阻塞读,同时读读与写写之间也会进行阻塞,可以想
办法将读操作和写操作分离开。
最常见的锁分离就是读写锁 ReadWriteLock,根据功能进行分离成读锁和写锁,这样读读不互斥,读写互斥,写写互斥,即保证了线程安全,又提高了性能,具体也请查看[高并发 Java 五] JDK 并发包 1。读写分离思想可以延伸,只要操作互不影响,锁就可以分离。比如LinkedBlockingQueue 从头部取出,从尾部放数据
锁粗化
有些情况下我们希望把很多次锁的请求合并成一个请求,以降低短时间内大量锁请求,同步,释放带来的性能损耗。
通常情况下,为了保证多线程间的有效并发,会要求每个线程持有锁的时间尽量短,即在使用完公共资源后,应该立即释放锁。但是,凡事都有一个度,如果对同一个锁不停的进行请求、同步和释放,其本身也会消耗系统宝贵的资源,反而不利于性能的优化
原则上,同步块的作用范围要尽量小。但是如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作在循环体内,频繁地进行互斥同步操作也会导致不必要的性能损耗。
锁粗化就是增大锁的作用域。
锁消除
锁消除是ava虚拟机在编译是,通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,通过
锁消除,可以节省毫无意义的请求锁时间。
锁消除是在编译器级别的事情。在即时编译器时,如果发现不可能被共享的对象,则可以消除这些对象的锁操作,多数是因为程序员编码不规范引起

自旋锁

自旋锁原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。线程自旋是需要消耗 cup 的,说白了就是让 cup 在做无用功,如果一直获取不到锁,那线程也不能一直占用 cup 自旋做无用功,所以需要设定一个自旋等待的最大时间。
如果持有锁的线程执行的时间超过自旋等待的最大时间扔没有释放锁,就会导致其它争用锁的线程在最大等待时间内还是获取不到锁,这时争用线程会停止自旋进入阻塞状态。

自旋锁的优缺点
自旋锁尽可能的减少线程的阻塞,这对于锁的竞争不激烈,且占用锁时间非常短的代码块来说性能能大幅度的提升,因为自旋的消耗会小于线程阻塞挂起再唤醒的操作的消耗,这些操作会导致线程发生两次上下文切换!
但是如果锁的竞争激烈,或者持有锁的线程需要长时间占用锁执行同步块,这时候就不适合使用自旋锁了,因为自旋锁在获取锁前一直都是占用 cpu 做无用功,占着 XX 不 XX,同时有大量线程在竞争一个锁,会导致获取锁的时间很长,线程自旋的消耗大于线程阻塞挂起操作的消耗,其它需要 cup 的线程又不能获取到 cpu,造成 cpu 的浪费。所以这种情况下我们要关闭自旋锁

很多 synchronized 里面的代码只是一些很简单的代码,执行时间非常快,此时等待的线程都加锁
可能是一种不太值得的操作,因为线程阻塞涉及到用户态和内核态切换的问题。既然 synchronized 里面的代码执行得非常快,不妨让等待锁的线程不要被阻塞,而是在 synchronized 的边界做忙循环,这就是自旋。如果做了多次循环发现还没有获得锁,再阻塞,这样可能是一种更 好的策略。
忙循环:就是程序员用循环让一个线程等待,不像传统方法wait(), sleep() 或 yield() 它们都放弃了 CPU控制,而忙循环不会放弃CPU,它就是在运行一个空循环。这么做的目的是为了保留CPU缓 存,在多核系统中,一个等待线程醒来的时候可能会在另一个内核运行,这样会重建缓存。为了避 免重建缓存和减少等待重建的时间就可以使用它了

自旋锁时间阈值(1.6 引入了适应性自旋锁)
自旋锁的目的是为了占着 CPU 的资源不释放,等到获取到锁立即进行处理。但是如何去选择自旋的执行时间呢 如果自旋执行时间太长,会有大量的线程处于自旋状态占用 CPU 资源,进而会影响整体系统的性能。因此自旋的周期选的额外重要,JVM 对于自旋周期的选择,jdk1.5 这个限度是一定的写死的,在 1.6 引入了适应性自旋锁,适应
性自旋锁意味着自旋的时间不在是固定的了,而是由前一次在同一个锁上的自旋时间以及锁的拥有者的状态来决定,基本认为一个线程上下文切换的时间是最佳的一个时间,同时 JVM 还针对当前 CPU 的负荷情况做了较多的优化,如果平均负载小于 CPUs 则一直自旋,如果有超过(CPUs/2)个线程正在自旋,则后来线程直接阻塞,如果正在自旋的线程发现 Owner 发生了变化则延迟自旋时间(自旋计数)或进入阻塞,如果 CPU 处于节电模式则停止自旋,自旋时间的最坏情况是 CPU的存储延迟(CPU A 存储了一个数据,到 CPU B 得知这个数据直接的时间差),自旋时会适当放弃线程优先级之间的差异。
自旋锁的开启
JDK1.6 中-XX:+UseSpinning 开启;
-XX:PreBlockSpin=10 为自旋次数;
JDK1.7 后,去掉此参数,由 jvm 控制

为什么要提出自旋锁
互斥同步对性能最大的影响是阻塞的实现,挂起线程和恢复线程的操作都需要转入内核态中完成,这些操作给系统的并发性带来很大压力。同时很多应用共享数据的锁定状态,只会持续很短的一段时间,为了这段时间去挂起和恢复线程并不值得。先不挂起线程,等一会儿。

自旋锁的原理
如果物理机器有一个以上的处理器,能让两个或以上的线程同时并行执行,让后面请求锁的线程稍等一会,但不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放。为了让线程等待,我们只需让线程执行一个忙循环(自旋)。

自旋的缺点
自旋等待本身虽然避免了线程切换的开销,但它要占用处理器时间。所以如果锁被占用的时间很短,自旋等待的效果就非常好;如果时间很长,那么自旋的线程只会白白消耗处理器的资源。所以自旋等待的时间要有一定的限度,如果自旋超过了限定的次数仍然没有成功获得锁,那就应该使用传统的方式挂起线程了。

什么是自适应自旋
自旋的时间不固定了,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。
• 如果一个锁对象,自旋等待刚刚成功获得锁,并且持有锁的线程正在运行,那么虚拟机认为这次自旋仍然可能成功,进而运行自旋等待更长的时间。
• 如果对于某个锁,自旋很少成功,那在以后要获取这个锁,可能省略掉自旋过程,以免浪费处理器资源。
有了自适应自旋,随着程序运行和性能监控信息的不断完善,虚拟机对程序锁的状况预测就会越来越准确,虚拟机也会越来越聪明。

公平锁和非公平锁

公平锁:就是比较公平,根据请求锁的顺序排列,先来请求的就先获取锁,后来获取锁就最 后获取到, 采用队列存放 类似于吃饭排队。
非公平锁:不是据请求的顺序排列, 通过争抢的方式获取锁。
非公平锁效率是公平锁效率要高,Synchronized 是非公平锁
New ReentramtLock()(true)—公平锁
New ReentramtLock()(false)—非公平锁
底层基于 aqs
所谓公平锁:就是在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程线程是等待队列的第一个,就占有锁,否则就会加入到等待队列中,以后会按照 FIFO 的规则从队列中取到自己
非公平锁:比较粗鲁,上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式
公平锁(Fair)
加锁前检查是否有排队等待的线程,优先排队等待的线程,先来先得
非公平锁(Nonfair)
加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待

  1. 非公平锁性能比公平锁高 5~10 倍,因为公平锁需要在多核的情况下维护一个队列
  2. Java 中的 synchronized 是非公平锁,ReentrantLock 默认的 lock()方法采用的是非公平锁。

公平锁底层是如何实现的

公平锁:就是比较公平,根据请求锁的顺序排列,先来请求的就先获取锁,后来获取锁就最后获取到, 采用队列存放 类似于吃饭排队
队列—底层实现方式—数组或者链表实现

可重入锁

如果一个线程中的多个流程能获取同一把锁,就使用可重入锁,如果线程的多个流程不能获取通一把锁,
就是用不可重入锁。
可重入锁又称为递归锁,是指同一个线程在外层方法获取了锁,在进入内层方法会自动获取锁。
可重入是多线程并发编程里面一个比较重要的概念。简单来说,就是在运行的某个方法或代码片段,因为抢占资源或者中断等原因导致方法或者代码片段的运行中断,等待中断程序执行结束后,重新进入到这个方法或者代码片段中运行,并且运行结果不会受到影响,那么这个方法或者代码片段就是可重入的。
而可重入锁,简单来说就是一个线程如果抢占到了互斥锁资源,在锁释放之前再去竞争同一把锁的时候,不需要等待,只需要记录重入次数。
在多线程并发编程里面,绝大部分锁都是可重入的,比如 synchronized、ReentrantLock 等,但是也有不支持
重入的锁,比如 JDK8 里面提供的读写锁StampedLock。
锁的可重入性,主要解决的问题是避免线程死锁的问题。因为一个已经获得同步锁 X 的线程,在释放锁 X 之前
再去竞争锁 X 的时候,相当于会出现自己要等待自己释放锁,这很显然是无法成立的。
“可重入锁”概念是:自己可以再次获取自己的内部锁。比如一个线程获得了某个对 象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不 可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增1,所以要等到锁的计数器 下降为0时才能释放锁

独占锁和共享锁

独占锁:在多线程中,只允许有一个线程获取到锁,其他线程都会等待。
JDK中的synchronized和J.U.C(java.util.concurrent)包中Lock的实现类都是独占锁。
共享锁:多个线程可以同时持有锁,例如 ReentrantLock 读写锁。读读可以共享、
写写互斥、读写互斥、写读互斥。在 JDK 中 ReentrantReadWriteLock 就是一种共享锁。

java 并发包提供的加锁模式分为独占锁和共享锁

独占锁模式下,每次只能有一个线程能持有锁,ReentrantLock 就是以独占方式实现的互斥锁。
独占锁是一种悲观保守的加锁策略,它避免了读/读冲突,如果某个只读线程获取锁,则其他读线
程都只能等待,这种情况下就限制了不必要的并发性,因为读操作并不会影响数据的一致性。

共享锁则允许多个线程同时获取锁,并发访问 共享资源,如:ReadWriteLock。共享锁则是一种
乐观锁,它放宽了加锁策略,允许多个执行读操作的线程同时访问共享资源

  1. AQS 的内部类 Node 定义了两个常量 SHARED 和 EXCLUSIVE,他们分别标识 AQS 队列中等
    待线程的锁获取模式。
  2. java 的并发包中提供了 ReadWriteLock,读-写锁。它允许一个资源可以被多个读操作访问,
    或者被一个 写操作访问,但两者不能同时进行。

分段锁

分段锁也并非一种实际的锁,而是一种思想 ConcurrentHashMap 是学习分段锁的最好实践

意向锁(Intent Lock)

意向锁(Intent Lock)是Java中的一种锁机制,用于在多个事务之间协调对共享资源的访问。它分为两种类型:共享锁(Shared Lock)和排他锁(Exclusive Lock)。

  1. 共享锁(Shared Lock):允许多个事务同时读取共享资源,但不允许其他事务修改或删除该资源。当一个事务获取了共享锁后,其他事务可以继续获取共享锁,但不能获取排他锁。
  2. 排他锁(Exclusive Lock):只允许一个事务修改或删除共享资源,其他事务不能读取或修改该资源。当一个事务获取了排他锁后,其他事务既不能获取共享锁,也不能获取排他锁。
    在Java中,可以使用java.util.concurrent.locks.ReentrantReadWriteLock类实现意向锁。以下是一个简单的示例:
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class IntentLockExample {
    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public void read() {
        lock.readLock().lock(); // 获取共享锁
        try {
            // 读取共享资源的代码
        } finally {
            lock.readLock().unlock(); // 释放共享锁
        }
    }

    public void write() {
        lock.writeLock().lock(); // 获取排他锁
        try {
            // 修改或删除共享资源的代码
        } finally {
            lock.writeLock().unlock(); // 释放排他锁
        }
    }
}

意向锁(Intent Locking)是Java并发编程中的一种机制,用于控制对共享资源的访问。它是一种轻量级的锁,相比于使用 synchronized 关键字或 ReentrantLock 类等重量级锁,意向锁的实现开销更小。
意向锁的主要目的是在多线程环境中保证共享资源的访问安全性。当一个线程试图访问共享资源时,它会先尝试获取意向锁,如果成功,则认为其他线程不会立即访问该资源,因此可以安全地进行访问。如果获取意向锁失败,则认为其他线程正在访问该资源,因此需要等待。
Java中的意向锁可以通过使用 volatile 关键字和 Unsafe 类来实现。volatile 关键字可以保证变量的可见性,即每个线程都可以正确地读取共享变量的最新值。Unsafe 类则可以用来执行各种低级别的内存操作,包括创建对象、分配内存、释放内存等。
使用意向锁时需要注意以下几点:

  1. 意向锁只能保证共享资源的访问安全性,不能保证线程的调度顺序。因此,在实现并发程序时,需要仔细考虑线程间的同步和通信问题。
  2. 意向锁只是一种轻量级的同步机制,适用于控制对共享资源的访问,但不适用于实现复杂的并发控制逻辑。如果需要实现更复杂的同步逻辑,应该使用 synchronized 关键字或 ReentrantLock 类等重量级锁。
  3. 意向锁的实现需要使用到内存屏障(Memory Barrier)等底层机制,因此需要谨慎处理内存访问顺序问题,以避免出现内存竞态条件(Race Condition)等问题。

意向锁(Intention Lock)是Java并发中的一个概念,主要用于ReentrantReadWriteLock读写锁。意向锁有两种类型:读意向锁和写意向锁。它们分别表示一个线程想要获取读锁或写锁。
在ReentrantReadWriteLock中,读锁和写锁是互斥的,即在同一时间只允许一个线程获取写锁,或者多个线程获取读锁。为了实现这个功能,ReentrantReadWriteLock使用了两个锁:读锁和写锁。
当一个线程尝试获取读锁时,它需要先获取读意向锁。如果此时写意向锁被其他线程持有,读锁获取失败。同样地,当一个线程尝试获取写锁时,它需要先获取写意向锁。如果此时读意向锁或写意向锁被其他线程持有,写锁获取失败。
意向锁的主要目的是为了提高锁的性能。在没有意向锁的情况下,当一个线程获取读锁时,其他线程可能会被阻塞,即使它们只想获取读锁。通过引入意向锁,可以避免这种情况,从而提高锁的性能。
以下是一个简单的Java代码示例,展示了如何使用ReentrantReadWriteLock:

import java.util.concurrent.locks.ReentrantReadWriteLock;
public class IntentionLockExample {
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void read() {
lock.readLock().lock();
try {
// 读操作
} finally {
lock.readLock().unlock();
}
}
public void write() {
lock.writeLock().lock();
try {
// 写操作
} finally {
lock.writeLock().unlock();
}
}
}

在这个示例中,我们使用了ReentrantReadWriteLock来实现读写锁。当一个线程尝试获取读锁时,它会先获取读意向锁;当一个线程尝试获取写锁时,它会先获取写意向锁。这样可以确保读锁和写锁之间的互斥关系,同时提高锁的性能。

InnoDB中的意向锁

在MySQL的InnoDB存储引擎中,意向锁是一种特殊类型的锁,用于指示事务将要在某一范围内的行上设置共享锁或排他锁。这有助于协调多个事务对数据行的访问。
意向锁分为两种类型:意向共享锁(IS)和意向排他锁(IX)。当一个事务打算对某些行设置共享锁时,它会在表级别设置意向共享锁。同样,当一个事务打算对某些行设置排他锁时,它会在表级别设置意向排他锁。
意向锁的存在有助于提高并发性能并减少死锁的可能性。这是因为其他事务在尝试对表中某些行设置锁之前,可以先检查表级别的意向锁,以确保它们不会产生冲突。
总的来说,意向锁是InnoDB存储引擎用来协调多个事务对数据行访问的一种机制,它在锁的层次结构中起着重要的作用,有助于保证数据的一致性和并发访问的效率。
InnoDB是MySQL中使用的默认存储引擎,它支持事务处理和行级锁定。意向锁是InnoDB中实现多版本并发控制(MVCC)的一种机制。
在InnoDB中,意向锁分为两种类型:共享意向锁(Shared Intent Lock)和排他意向锁(Exclusive Intent Lock)。

  1. 共享意向锁(Shared Intent Lock):多个事务同时对同一行数据进行读取操作时,会先尝试获取共享意向锁。如果成功,表示其他事务不会立即对该行数据进行修改操作,因此可以安全地进行读取操作。
  2. 排他意向锁(Exclusive Intent Lock):当一个事务需要对某行数据进行修改操作时,会先尝试获取排他意向锁。如果成功,表示该事务可以执行修改操作,同时其他事务不能同时获取共享意向锁或排他意向锁。
    在InnoDB中,意向锁是通过在事务中维护一个意向锁列表来实现的。当一个事务需要获取意向锁时,会在意向锁列表中添加相应的记录。当事务执行修改操作时,会检查意向锁列表中是否存在其他事务持有了对该行的意向锁,如果存在,则需要进行等待或回滚操作。
    需要注意的是,意向锁只是一种轻量级的锁定机制,它不能保证线程的调度顺序,因此需要谨慎处理并发访问和同步问题。此外,InnoDB还支持更高级别的锁定机制,如行级锁定和表级锁定等。

InnoDB中的意向锁是表级别的锁,这种锁设计的初衷是为了在支持多粒度锁的机制中,解决行级锁和表级锁的共存问题。它不会与行级锁产生冲突,可以提前声明未来某个时刻事务可能要加共享或排他锁的意愿。
具体来说,意向锁有两种类型:一种是意向共享锁(intention shared lock, IS),另一种是意向排他锁(intention exclusive lock, IX)。当一个事务想要获取某些行的共享锁时,它必须先获得表的意向共享锁;当事务想要获取某些行的排他锁时,它必须先获得表的意向排他锁。例如,我们可以通过以下SQL语句设置意向共享锁:SELECT column FROM table ... LOCK IN SHARE MODE;
此外,当我们使用MySQL InnoDB在Repeatable-Read的事务隔离级别下,会使用插入意向锁来控制和解决并发插入的问题,它是一种特殊类型的间隙锁。

InnoDB是一个高性能的、开源的数据库管理系统,支持事务处理和行级锁。
在InnoDB中,意向锁(Intention Lock)被用于表级锁和行级锁。意向锁可以避免死锁,提高并发性能。
在InnoDB中,意向锁有两种类型:

  1. 表级锁的意向锁(Table Intention Lock):表级锁的意向锁是InnoDB存储引擎中的一种锁类型,用于维护表级锁。当一个事务准备在表A上获取行级锁时,必须先获取表级锁。此时,事务会在表A的元数据上放置一个表级锁的意向锁。如果此时有其他事务正在表A上获取行级锁,那么这些事务将等待表级锁的意向锁释放。
  2. 行级锁的意向锁(Row Intention Lock):行级锁的意向锁是InnoDB存储引擎中的一种锁类型,用于维护行级锁。当一个事务准备在表A中的行B上获取行级锁时,必须先获取行级锁。此时,事务会在表A的元数据(即行B的元数据)上放置一个行级锁的意向锁。如果此时有其他事务正在表A中的行B上获取行级锁,那么这些事务将等待行级锁的意向锁释放。
    意向锁的存在可以避免死锁。例如,如果两个事务A和B都试图在表A的同一行上获取行级锁,那么事务A需要等待事务B释放行级锁,而事务B也需要等待事务A释放行级锁。如果此时没有意向锁,那么就会导致死锁。但是,通过意向锁,可以使得事务A等待的是表级锁的意向锁的释放,而事务B等待的是行级锁的意向锁的释放,这样可以避免死锁。
    需要注意的是,意向锁是InnoDB存储引擎特有的,不是SQL标准的。在SQL标准中,只有行级锁和表级锁,没有意向锁。

InnoDB中的意向锁 是行锁吗
InnoDB中的意向锁(Intention Locks)不是行锁,而是表级锁和行级锁的辅助锁。意向锁的主要目的是避免死锁,提高并发性能。
在InnoDB中,意向锁有两种类型:

  1. 表级锁的意向锁(Table Intention Lock):表级锁的意向锁是InnoDB存储引擎中的一种锁类型,用于维护表级锁。当一个事务准备在表A上获取行级锁时,必须先获取表级锁。此时,事务会在表A的元数据上放置一个表级锁的意向锁。如果此时有其他事务正在表A上获取行级锁,那么这些事务将等待表级锁的意向锁释放。
  2. 行级锁的意向锁(Row Intention Lock):行级锁的意向锁是InnoDB存储引擎中的一种锁类型,用于维护行级锁。当一个事务准备在表A中的行B上获取行级锁时,必须先获取行级锁。此时,事务会在表A的元数据(即行B的元数据)上放置一个行级锁的意向锁。如果此时有其他事务正在表A中的行B上获取行级锁,那么这些事务将等待行级锁的意向锁释放。

意向锁的存在可以避免死锁。例如,如果两个事务A和B都试图在表A的同一行上获取行级锁,那么事务A需要等待事务B释放行级锁,而事务B也需要等待事务A释放行级锁。如果此时没有意向锁,那么就会导致死锁。但是,通过意向锁,可以使得事务A等待的是表级锁的意向锁的释放,而事务B等待的是行级锁的意向锁的释放,这样可以避免死锁。

需要注意的是,意向锁是InnoDB存储引擎特有的,不是SQL标准的。在SQL标准中,只有行级锁和表级锁,没有意向锁。

InnoDB中的意向锁不是行锁,而是一种表级别的锁。它们用于指示事务将要在某一范围内的行上设置共享锁或排他锁,从而协调多个事务对数据行的访问。
意向锁分为意向共享锁(IS)和意向排他锁(IX),它们并不直接锁定实际的行数据,而是用于指示其他事务在特定范围内可能存在的共享锁或排他锁情况。这有助于其他事务在尝试获取行级锁时进行冲突检测,从而提高并发性能并减少死锁的可能性。
因此,虽然意向锁影响了行级锁的获取和释放,但它本身并不是针对实际行数据的锁定,而是在表级别上协调行级锁的使用。
联锁
在Java中,"联锁"通常指的是多个线程对共享资源的同步访问。这是通过使用Java的内置并发工具实现的,如synchronized关键字和java.util.concurrent.locks包中的类。
一种常见的联锁情况是多个线程需要同时访问和修改共享数据。为了确保数据的一致性和防止数据竞争,Java提供了同步机制来协调线程的访问。
以下是一些常见的Java联锁方法:

  1. 使用synchronized关键字:通过在方法或代码块上使用synchronized关键字,可以创建一个临界区,一次只允许一个线程进入临界区。这可以防止多个线程同时修改共享资源。
public class SharedResource {  
    private int counter = 0;  
      
    public synchronized void increment() {  
        counter++;  
    }  
}
  1. 使用ReentrantLock类:ReentrantLock是Java中的一个可重入锁,它提供了比synchronized关键字更灵活的同步机制。它支持公平和非公平两种模式,并且可以灵活地控制锁的粒度。
import java.util.concurrent.locks.ReentrantLock;  
  
public class SharedResource {  
    private final ReentrantLock lock = new ReentrantLock();  
    private int counter = 0;  
      
    public void increment() {  
        lock.lock();  
        try {  
            counter++;  
        } finally {  
            lock.unlock();  
        }  
    }  
}
  1. 使用java.util.concurrent.locks.ReadWriteLock接口:对于需要区分读和写操作的场景,可以使用ReadWriteLock接口。它允许多个线程同时读取共享资源,但在写入时只允许一个线程访问。
import java.util.concurrent.locks.ReadWriteLock;  
import java.util.concurrent.locks.ReentrantReadWriteLock;  
  
public class SharedResource {  
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();  
    private int counter = 0;  
      
    public void increment() {  
        readWriteLock.writeLock().lock();  
        try {  
            counter++;  
        } finally {  
            readWriteLock.writeLock().unlock();  
        }  
    }  
      
    public int getCounter() {  
        readWriteLock.readLock().lock();  
        try {  
            return counter;  
        } finally {  
            readWriteLock.readLock().unlock();  
        }  
    }  
}

这些是Java中常见的联锁方法。选择哪种方法取决于你的具体需求和应用的场景。在大多数情况下,使用synchronized关键字是足够的,因为它提供了简单且高效的同步机制。然而,对于更复杂的同步需求,可能需要使用更高级的锁机制,如ReentrantLock或ReadWriteLock。

  • 43
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

思静语

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值