温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

java中锁的知识点有哪些

发布时间:2022-04-16 10:20:41 来源:亿速云 阅读:182 作者:zzz 栏目:编程语言

Java中锁的知识点有哪些

在Java编程中,锁(Lock)是一种用于控制多个线程对共享资源访问的机制。锁的使用可以避免多个线程同时访问共享资源而导致的数据不一致性问题。Java提供了多种锁机制,包括内置锁(synchronized关键字)、显式锁(ReentrantLock类)、读写锁(ReentrantReadWriteLock类)等。本文将详细介绍Java中锁的相关知识点。

1. 内置锁(synchronized关键字)

1.1 synchronized关键字的基本用法

synchronized是Java中最基本的锁机制,它可以用来修饰方法或代码块,确保同一时间只有一个线程可以执行被修饰的代码。

1.1.1 修饰实例方法

public synchronized void method() { // 线程安全的代码 } 

当一个线程调用一个被synchronized修饰的实例方法时,它会获取该实例对象的锁,其他线程必须等待该锁释放后才能执行该方法。

1.1.2 修饰静态方法

public static synchronized void staticMethod() { // 线程安全的代码 } 

当一个线程调用一个被synchronized修饰的静态方法时,它会获取该类的Class对象的锁,其他线程必须等待该锁释放后才能执行该方法。

1.1.3 修饰代码块

public void method() { synchronized (this) { // 线程安全的代码 } } 

synchronized代码块可以指定一个对象作为锁,当线程进入代码块时,它会尝试获取该对象的锁,其他线程必须等待该锁释放后才能进入代码块。

1.2 synchronized的锁机制

synchronized关键字使用的是内置锁(也称为监视器锁),它是一种可重入锁(Reentrant Lock)。可重入锁意味着同一个线程可以多次获取同一个锁,而不会导致死锁。

public class ReentrantExample { public synchronized void outer() { inner(); } public synchronized void inner() { // 线程安全的代码 } } 

在上面的例子中,outer方法和inner方法都被synchronized修饰。当一个线程调用outer方法时,它会获取该实例对象的锁,然后在outer方法中调用inner方法时,它会再次获取同一个锁。由于synchronized是可重入锁,因此不会发生死锁。

1.3 synchronized的局限性

synchronized关键字虽然简单易用,但它也有一些局限性:

  • 无法中断:当一个线程获取锁后,其他线程必须等待该锁释放,无法中断等待的线程。
  • 无法设置超时synchronized无法设置获取锁的超时时间,如果锁一直被占用,其他线程将一直等待。
  • 无法实现公平锁synchronized无法保证等待锁的线程按照申请锁的顺序获取锁,可能会导致某些线程长时间无法获取锁。

2. 显式锁(ReentrantLock类)

为了克服synchronized的局限性,Java提供了java.util.concurrent.locks包中的ReentrantLock类。ReentrantLock是一种显式锁,它提供了比synchronized更灵活的锁机制。

2.1 ReentrantLock的基本用法

import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ReentrantLockExample { private final Lock lock = new ReentrantLock(); public void method() { lock.lock(); // 获取锁 try { // 线程安全的代码 } finally { lock.unlock(); // 释放锁 } } } 

ReentrantLock的使用方式与synchronized类似,但它需要显式地调用lock()方法获取锁,并在finally块中调用unlock()方法释放锁,以确保锁一定会被释放。

2.2 ReentrantLock的可重入性

synchronized一样,ReentrantLock也是一种可重入锁。同一个线程可以多次获取同一个锁,而不会导致死锁。

public class ReentrantLockExample { private final Lock lock = new ReentrantLock(); public void outer() { lock.lock(); try { inner(); } finally { lock.unlock(); } } public void inner() { lock.lock(); try { // 线程安全的代码 } finally { lock.unlock(); } } } 

在上面的例子中,outer方法和inner方法都使用了同一个ReentrantLock对象。当一个线程调用outer方法时,它会获取锁,然后在outer方法中调用inner方法时,它会再次获取同一个锁。由于ReentrantLock是可重入锁,因此不会发生死锁。

2.3 ReentrantLock的高级特性

ReentrantLock提供了比synchronized更高级的特性,包括:

2.3.1 可中断锁

ReentrantLock提供了lockInterruptibly()方法,允许线程在等待锁的过程中响应中断。

public void method() throws InterruptedException { lock.lockInterruptibly(); // 可中断的获取锁 try { // 线程安全的代码 } finally { lock.unlock(); // 释放锁 } } 

如果一个线程在等待锁的过程中被中断,它将抛出InterruptedException,从而可以响应中断。

2.3.2 超时锁

ReentrantLock提供了tryLock()方法,允许线程在指定的时间内尝试获取锁,如果超时仍未获取到锁,则返回false

public void method() { if (lock.tryLock(1, TimeUnit.SECONDS)) { // 尝试在1秒内获取锁 try { // 线程安全的代码 } finally { lock.unlock(); // 释放锁 } } else { // 超时未获取到锁的处理逻辑 } } 

tryLock()方法可以避免线程长时间等待锁,从而提高程序的响应性。

2.3.3 公平锁

ReentrantLock提供了公平锁的机制,即按照线程申请锁的顺序来获取锁。公平锁可以避免某些线程长时间无法获取锁的问题。

private final Lock fairLock = new ReentrantLock(true); // 创建公平锁 

公平锁虽然可以避免线程饥饿问题,但由于需要维护一个等待队列,因此性能通常比非公平锁要低。

2.4 ReentrantLock与synchronized的比较

特性 synchronized ReentrantLock
可重入性
可中断锁
超时锁
公平锁
性能 较高 较低
使用复杂度 简单 较复杂

3. 读写锁(ReentrantReadWriteLock类)

在某些场景下,读操作远远多于写操作,如果使用普通的锁机制,会导致读操作和写操作互斥,从而降低并发性能。为了解决这个问题,Java提供了ReentrantReadWriteLock类,它允许多个读线程同时访问共享资源,但在写线程访问时,所有读线程和其他写线程都会被阻塞。

3.1 ReentrantReadWriteLock的基本用法

import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class ReadWriteLockExample { private final ReadWriteLock rwLock = new ReentrantReadWriteLock(); public void readMethod() { rwLock.readLock().lock(); // 获取读锁 try { // 读操作 } finally { rwLock.readLock().unlock(); // 释放读锁 } } public void writeMethod() { rwLock.writeLock().lock(); // 获取写锁 try { // 写操作 } finally { rwLock.writeLock().unlock(); // 释放写锁 } } } 

在上面的例子中,readMethod方法获取读锁,允许多个线程同时执行读操作;writeMethod方法获取写锁,确保同一时间只有一个线程执行写操作。

3.2 读写锁的特性

  • 读锁共享:多个线程可以同时获取读锁,读锁之间不会互斥。
  • 写锁独占:写锁是独占的,同一时间只能有一个线程获取写锁,且写锁与读锁互斥。
  • 锁降级ReentrantReadWriteLock支持锁降级,即一个线程在持有写锁的情况下可以获取读锁,然后在释放写锁后继续持有读锁。
public void lockDowngrade() { rwLock.writeLock().lock(); // 获取写锁 try { // 写操作 rwLock.readLock().lock(); // 获取读锁(锁降级) } finally { rwLock.writeLock().unlock(); // 释放写锁 } try { // 读操作 } finally { rwLock.readLock().unlock(); // 释放读锁 } } 

锁降级可以确保在写操作完成后,读操作仍然可以继续持有锁,从而避免其他写线程修改数据。

3.3 读写锁的性能

读写锁适用于读多写少的场景,可以显著提高并发性能。然而,如果写操作频繁,读写锁的性能可能会低于普通的锁机制,因为写锁的获取会导致所有读线程和其他写线程阻塞。

4. 其他锁机制

除了synchronizedReentrantLockReentrantReadWriteLock之外,Java还提供了其他一些锁机制,用于特定的并发场景。

4.1 StampedLock

StampedLock是Java 8引入的一种新的锁机制,它提供了三种模式的锁:写锁、悲观读锁和乐观读锁。StampedLock的性能通常比ReentrantReadWriteLock更高,尤其是在读多写少的场景下。

import java.util.concurrent.locks.StampedLock; public class StampedLockExample { private final StampedLock stampedLock = new StampedLock(); public void writeMethod() { long stamp = stampedLock.writeLock(); // 获取写锁 try { // 写操作 } finally { stampedLock.unlockWrite(stamp); // 释放写锁 } } public void readMethod() { long stamp = stampedLock.readLock(); // 获取悲观读锁 try { // 读操作 } finally { stampedLock.unlockRead(stamp); // 释放读锁 } } public void optimisticReadMethod() { long stamp = stampedLock.tryOptimisticRead(); // 获取乐观读锁 // 读操作 if (!stampedLock.validate(stamp)) { // 检查乐观读锁是否有效 stamp = stampedLock.readLock(); // 获取悲观读锁 try { // 读操作 } finally { stampedLock.unlockRead(stamp); // 释放读锁 } } } } 

StampedLock的乐观读锁允许在不阻塞写线程的情况下进行读操作,从而提高并发性能。然而,乐观读锁的有效性需要通过validate()方法进行检查,如果发现数据已被修改,则需要重新获取悲观读锁。

4.2 Condition

ConditionReentrantLock提供的一种线程等待/通知机制,类似于Objectwait()notify()方法。Condition允许线程在特定条件下等待,并在条件满足时被唤醒。

import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ConditionExample { private final Lock lock = new ReentrantLock(); private final Condition condition = lock.newCondition(); private boolean flag = false; public void await() throws InterruptedException { lock.lock(); try { while (!flag) { condition.await(); // 等待条件满足 } // 条件满足后的操作 } finally { lock.unlock(); } } public void signal() { lock.lock(); try { flag = true; condition.signal(); // 唤醒等待的线程 } finally { lock.unlock(); } } } 

Condition的使用方式与wait()notify()类似,但它提供了更灵活的线程等待/通知机制,可以创建多个Condition对象,从而实现更复杂的线程同步。

5. 锁的性能优化

在高并发场景下,锁的性能对系统的整体性能有着重要影响。以下是一些常见的锁性能优化策略:

5.1 减少锁的粒度

锁的粒度越小,锁的竞争就越少,从而提高并发性能。例如,可以将一个大的同步代码块拆分为多个小的同步代码块,或者使用细粒度的锁(如ConcurrentHashMap中的分段锁)。

5.2 使用无锁数据结构

在某些场景下,可以使用无锁数据结构(如AtomicIntegerAtomicReference等)来替代锁,从而避免锁的开销。

5.3 使用读写锁

在读多写少的场景下,使用读写锁可以显著提高并发性能。

5.4 避免死锁

死锁是指多个线程互相等待对方释放锁,从而导致所有线程都无法继续执行。为了避免死锁,可以遵循以下原则:

  • 按顺序获取锁:所有线程按照相同的顺序获取锁,避免交叉获取锁。
  • 设置超时时间:使用tryLock()方法设置获取锁的超时时间,避免线程长时间等待锁。
  • 避免嵌套锁:尽量避免在一个锁的代码块中获取另一个锁。

6. 总结

Java提供了多种锁机制,包括内置锁(synchronized)、显式锁(ReentrantLock)、读写锁(ReentrantReadWriteLock)等。每种锁机制都有其适用的场景和优缺点。在实际开发中,应根据具体的需求选择合适的锁机制,并通过优化锁的粒度、使用无锁数据结构等策略来提高并发性能。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI