java中锁的知识点有哪些

发布时间:2022-04-16 10:20:41 作者:zzz
来源:亿速云 阅读:165

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关键字虽然简单易用,但它也有一些局限性:

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 读写锁的特性

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 避免死锁

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

6. 总结

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

推荐阅读:
  1. java中的锁有多少种
  2. MySQL锁的知识点有哪些

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

java

上一篇:MySQL的GTID复制怎么应用

下一篇:php如何比较2个数组是否不同

相关阅读

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

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