在Java中对于多线程来说,锁是一种重要且必不可少的东西,那么我们将如何使用以及在什么时候使用什么样的锁呢?请各位往下看
悲观锁VS乐观锁
悲观锁:
在多线程环境中,冲突是非常常见的,所以在执行操作之前,悲观锁就会先把这个资源进行加锁,用来保证数据的一致性和完整性,阻止了其他线程同一时间对这个数据进行修改,但是可能会降低并发性能,因为只有等到锁释放其他线程才能继续执行
乐观锁:
假设数据一般不会产生冲突,所以当数据进行提交更新的时候,才会对这个数据进行检测来查看是否发生了并行冲突,对比与悲观锁来说是一个更轻量级的锁,一般用于读操作更多的时候,当数据冲突较少的时候,乐观锁能极大的提高并发性能
什么时候用乐观锁比较好,什么时候用悲观锁比较好?
当读操作远多于写操作的时候,我们就可以用乐观锁,减少锁的开销,提高并发性能。当多个线程频繁的修改同一份数据的时候,我们就可以使用悲观锁来保证数据的一致性和完整性,避免数据产生冲突。
重量级锁VS轻量级锁
重量级锁:
重量级锁依赖于操作系统层面的互斥量(mutex)来实现,这种锁机制涉及到用户态和内核态之间的切换,开销较大。当一个线程获取重量级锁的时候,这个线程就会独占该锁所保护的资源,当其他线程想要获取这把锁的时候就会被阻塞,直到锁被释放
轻量级锁:
通常依赖于CAS操作来尝试获取锁,在冲突较少的情况下,轻量级锁可以显著的提高并发性能,但是如果多次获取锁失败则可能会变成重量级锁
- 轻量级锁:使用 CAS 操作尝试获取锁,尽量避免阻塞线程。
- 重量级锁:涉及到线程的阻塞和挂起,需要操作系统层面的支持,性能开销更大。
自旋锁VS挂起等待锁
自旋锁:
当线程加锁失败后,不会让线程进入阻塞状态,而是在一个循环里面不断获取这把锁,直到拿到为止,但是假如这把锁被持有的时间很长,就会导致CPU资源浪费,因为线程会不断的检查这把锁的状态
挂起等待锁:
当一个线程已经拥有锁了,但是另一个线程尝试获取这个锁时,就无法立刻获得,此时,这个未获取锁的线程就会被挂起并放入等待队列中,直到锁被释放,相比较于自旋锁来说不用频繁的获取锁的状态,挂起等待的时候是不消耗CPU资源的
- 自旋锁:能第一时间获取到锁,不涉及线程调度和阻塞,但是如果锁持有的时间较长就会消耗大量的CPU
- 挂起等待锁:挂起等待是不消耗CPU的,适用于数据修改较少的场景,并且挂起等待锁是重量级锁的一个典型特征
公平锁VS非公平锁
什么叫做公平,是先来后到叫做公平还是概率平均叫做公平,其实都是,但是对于公平锁来说遵循先来后到的原则是公平,而非公平锁则是遵循概率均等
公平锁:
当一个线程尝试获取锁的时候,会先检查该锁是否可用,如果可用则获取锁并继续执行,如果不可用则进入等待队列,直到锁被释放,按照进入等待队列的顺序拿到锁并使用
非公平锁:
非公平锁会先检查当前的锁是否可用,如果可用则获取锁并继续执行,如果不可用则可能会忽略等待队列中的线程,并允许当前线程继续获取锁,这就导致了可能某些线程会进行长时间的等待,从而导致“饿死”
可重入锁和不可重入锁
可重入锁:
在Java中,synchronized 这个关键字本身就帮我们实现了可重入锁这个功能,可重入性:同一个线程可以多次获取同一把锁,从而减少了死锁的概率
不可重入锁:
不允许同一个线程多次获取同一把锁,如果一个线程试图再次获取它所持有的锁,此时就会导致死锁
读写锁
读锁:
允许多个线程同时进行读操作,适用于读多写少的场景,提高并发性能
写锁:
只允许一个线程进行读操作,一个线程进行写操作
- 读加锁和读加锁之间, 不互斥
- 写加锁和写加锁之间, 互斥
- 读加锁和写加锁之间, 互斥