java并发锁的实现

 更新时间:2024年04月11日 09:23:20   作者:Flying_Fish_roe  
Java中的锁主要是为了解决多个线程访问共享数据时的竞争问题,确保线程能够安全地访问和修改共享数据,本文主要介绍了java并发锁的实现,感兴趣的可以了解一下

ReentrantLock 

ReentrantLock是Java并发编程中的一种锁机制。它的基本流程如下:

  • 创建ReentrantLock对象。
  • 在需要加锁的代码块前调用lock()方法,该方法会尝试获取锁,如果锁已被其他线程占用,则当前线程会被阻塞。
  • 执行需要加锁的代码。
  • 在加锁代码块的finally语句块中调用unlock()方法来释放锁。

ReentrantLock的特点和用法如下:

  • 可重入性:ReentrantLock是可重入锁,即同一个线程可以重复获取该锁,而不会发生死锁。这是通过维护一个持有锁的线程的引用计数来实现的。
  • 公平性:ReentrantLock可以指定是公平锁还是非公平锁,默认情况下是非公平锁。公平锁是按照线程申请锁的顺序来分配锁,而非公平锁则是随机分配锁,可能会导致某些线程饥饿。
  • 条件变量:ReentrantLock提供了Condition接口的实现,可以通过该接口实现对线程的等待和唤醒机制,更灵活地控制线程的同步。
  • 可中断:ReentrantLock提供了lockInterruptibly()方法,如果当前线程还没有获取到锁,但是被其他线程中断,可以通过该方法响应中断。

以下是一个使用ReentrantLock的Java代码示例:

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        // 创建并启动多个线程
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(new MyThread());
            thread.start();
        }
    }

    static class MyThread implements Runnable {
        @Override
        public void run() {
            try {
                // 加锁
                lock.lock();
                // 执行需要加锁的代码
                System.out.println("Thread " + Thread.currentThread().getId() + " is running");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // 释放锁
                lock.unlock();
            }
        }
    }
}

在上述示例中,我们创建了一个ReentrantLock对象,并在MyThread的run()方法中加锁、执行代码、释放锁。在main方法中,我们创建并启动了5个线程,它们会依次获取锁并执行代码。由于ReentrantLock是可重入锁,同一个线程可以多次获取锁,所以每个线程都可以成功地执行代码块。

ReentrantReadWriteLock

ReentrantReadWriteLock是Java并发编程中的一个锁机制,它是一种读写锁,允许多个线程同时读共享资源,但只能有一个线程写资源。ReentrantReadWriteLock在实现上通过两个锁来实现,一个是读锁(共享锁),一个是写锁(独占锁)。

基本流程如下:

  • 多个线程可以同时获取读锁,读锁之间不互斥,可以并发执行。
  • 获取写锁的线程会阻塞其他线程的读锁和写锁,只有释放写锁后才允许其他线程获取读写锁。

ReentrantReadWriteLock的特点和用法:

  • 公平性:可以选择公平模式或非公平模式,默认是非公平模式。在非公平模式下,允许锁被后来的线程插队,以提高吞吐量;在公平模式下,锁会按照请求的顺序分配给线程,保证公平性。
  • 重入性:与ReentrantLock一样,ReentrantReadWriteLock可以重入,同一个线程可以多次获取读锁或写锁。
  • 锁降级:一个线程拥有写锁的时候,可以先获取读锁,然后再释放写锁,这样就实现了锁的降级。
  • 锁升级:读锁不能升级为写锁,因为会有死锁的风险。

下面是一个简单的示例代码,展示了ReentrantReadWriteLock的用法:

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class MyReadWriteLock {
    private int value = 0;
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public int getValue() {
        lock.readLock().lock(); // 获取读锁
        try {
            return value;
        } finally {
            lock.readLock().unlock(); // 释放读锁
        }
    }

    public void increment() {
        lock.writeLock().lock(); // 获取写锁
        try {
            value++;
        } finally {
            lock.writeLock().unlock(); // 释放写锁
        }
    }
}

在上面的示例中,MyReadWriteLock类包含一个value变量和一个ReentrantReadWriteLock对象。getValue()方法获取读锁,读取value的值并返回。increment()方法获取写锁,将value加1。通过使用读写锁,多个线程可以同时读取value的值,但只有一个线程可以写入value的值。

Condition

Condition是Java并发编程中的一种同步机制,它可以用于实现线程之间的等待和通知。

基本流程:

  • 创建一个Lock对象,通过Lock对象的newCondition()方法创建一个Condition对象。
  • 通过Lock对象的lock()方法获取锁。
  • 在某个线程中,通过Condition对象的await()方法使线程等待,同时释放锁。
  • 在另一个线程中,通过Condition对象的signal()或signalAll()方法进行通知。
  • 在第一个线程中,通过Condition对象的await()方法再次获取锁并继续执行。

特点和用法:

  • 可以与Lock对象配合使用,对某个共享资源进行互斥访问和条件等待。
  • 可以精确地控制线程的等待和通知。

示例代码:

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 waitForFlag() throws InterruptedException {
        lock.lock();
        try {
            while (!flag) {
                condition.await(); // 线程等待并释放锁
            }
        } finally {
            lock.unlock();
        }
        System.out.println("Flag is true, continue executing.");
    }

    public void setFlag() {
        lock.lock();
        try {
            flag = true;
            condition.signalAll(); // 发送通知并唤醒等待线程
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        final ConditionExample example = new ConditionExample();

        Thread waitingThread = new Thread(() -> {
            try {
                example.waitForFlag();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread settingThread = new Thread(() -> {
            try {
                Thread.sleep(2000); // 模拟执行耗时操作
                example.setFlag();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        waitingThread.start();
        settingThread.start();

        waitingThread.join();
        settingThread.join();
    }
}

在上述示例中,有两个线程,一个线程等待flag变量为true,另一个线程在某一时刻将flag设置为true。通过Condition对象的await()方法使等待线程进入等待状态,并释放锁,直到另一个线程通过Condition对象的signalAll()方法发送通知并唤醒等待线程,等待线程再次获取锁并继续执行。

应用场景

不同的锁机制应对的问题不同,在使用时需要根据具体的应用场景进行选择。

  • synchronized 锁适用于互斥场景,代码粒度小,适合在单线程或少量并发的情况下使用。

  • Lock 锁适用于复杂的并发场景,通过支持公平性、可中断等待锁等特点,提高了系统的性能。

  • ReentrantLock 锁是 Lock 接口的实现类,支持可重入、可中断等待等特点,适用于异步线程操作。

  • ReadWriteLock 锁适用于读写性能比较高的场景,在读多写少的情况下可以提高并发性能。

  • StampedLock 锁适用于读多写少的场景,在使用时需要根据实际场景选择乐观锁或悲观锁,提高了并发性能。

总结 

Java并发体系中的锁是用来管理多个线程对共享资源的访问的工具。锁的使用可以确保多个线程之间的同步和互斥,从而避免竞态条件和数据的不一致性。

Java中的锁可以分为两大类:内置锁和显式锁。

内置锁:

  • synchronized关键字:synchronized是Java中最基本的内置锁机制。它可以修饰方法或代码块,一次只允许一个线程访问被修饰的代码块或方法。当一个线程获得锁时,其他线程必须等待锁释放才能继续执行。
  • 锁对象:synchronized还可以用于指定一个对象作为锁。当一个线程获得该对象的锁时,其他线程对该对象的访问将被阻塞。这种方式可以实现更细粒度的锁控制。

显式锁:

  • ReentrantLock类:ReentrantLock是Java提供的显式锁的实现类。它提供了与synchronized类似的功能,但提供了更灵活的锁控制。通过lock()方法获取锁,通过unlock()方法释放锁。ReentrantLock还提供了一些其他功能,如可中断锁、公平锁等。
  • Condition接口:Condition接口是与显式锁ReentrantLock配合使用的重要组件。它可以让线程在等待某个条件满足时暂时释放锁,从而避免了线程一直占用锁资源而无法执行其他任务。

锁的选择应根据具体的需求和场景来决定。synchronized是最简单和常用的锁机制,适用于大部分情况。ReentrantLock提供了更多的灵活性和高级功能,例如可中断锁、公平锁等,但使用起来相对复杂一些。在多个线程需要等待某个条件满足时,使用Condition接口可以更好地控制线程的等待和唤醒。

到此这篇关于java并发锁的实现的文章就介绍到这了,更多相关java并发锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

最新评论