java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java锁机制

Java锁机制详细讲解与实战应用

作者:小码快撩

本文全面介绍了Java中的锁机制,包括synchronized关键字、内置锁、显示锁、读写锁、条件变量、乐观锁、悲观锁、自旋锁和StampedLock,通过实际代码示例,详细讲解了每种锁的实现方法和应用场景,感兴趣的朋友跟随小编一起看看吧

引言

在多线程编程中,确保数据的并发访问安全是至关重要的。Java作为支持多线程编程的主流语言,提供了多种内置和高级锁机制来解决共享资源的竞争问题,从而保证线程间的同步与协作。本文将全面探讨Java中的锁机制,从基本概念、类型到具体实现方法,并结合实际应用场景进行说明。

一、synchronized关键字

 Java中的synchronized关键字是一种内置锁机制,用于保证多线程环境下的线程安全。它提供了简单易用的互斥访问控制,确保同一时刻只有一个线程可以执行特定代码块。

下面是一个使用synchronized关键字的示例代码:

public class SynchronizedExample {
    private int counter;
    public synchronized void increment() {
        counter++;
    }
    public synchronized int getCount() {
        return counter;
    }
}

在上面的示例中,increment() 和 getCount() 方法都使用了 synchronized 关键字。这意味着当一个线程正在执行其中一个方法时,其他线程想要执行另一个方法将被阻塞,直到当前线程执行完毕并释放锁。

synchronized 关键字可以用于以下场景:

public class SynchronizedStaticExample {
    private static int counter;
    public static synchronized void increment() {
        counter++;
    }
    public static synchronized int getCount() {
        return counter;
    }
}
public class SynchronizedBlockExample {
    private int counter;
    private Object lock = new Object();
    public void increment() {
        synchronized(lock) {
            counter++;
        }
    }
    public int getCount() {
        synchronized(lock) {
            return counter;
        }
    }
}

在上面的示例中,increment() 和 getCount() 方法内部的同步代码块使用了同一个锁对象 lock。这意味着当一个线程访问其中一个同步代码块时,其他线程想要访问另一个同步代码块将被阻塞,直到当前线程执行完毕并释放锁。

二、内置锁(Intrinsic Locks or Monitor Locks)

 Java内置锁(Intrinsic Locks 或 Monitor Locks)是基于JVM实现的一种同步机制,每个Java对象都可以关联一个内置锁。当线程试图访问被 synchronized 关键字修饰的方法或代码块时,会尝试获取该对象的内置锁,如果成功,则可以执行相应的临界区代码;如果失败(即锁已被其他线程持有),则当前线程将进入阻塞状态,等待锁释放。

以下是一个使用Java内置锁的示例代码:

public class IntrinsicLockExample {
    private int counter = 0;
    // 同步实例方法,隐式使用 this 对象作为锁
    public synchronized void increment() {
        counter++;
    }
    // 同步代码块,显式指定锁对象
    public void incrementWithBlock(Object lock) {
        synchronized (lock) {
            counter++;
        }
    }
    // 获取当前计数
    public synchronized int getCount() {
        return counter;
    }
    // 示例类中的静态变量和对应的同步方法
    private static int staticCounter = 0;
    public static synchronized void incrementStatic() {
        staticCounter++;
    }
}
// 使用示例
public class Main {
    public static void main(String[] args) {
        IntrinsicLockExample example = new IntrinsicLockExample();
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.increment();
            }
        });
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.increment();
            }
        });
        thread1.start();
        thread2.start();
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Final count: " + example.getCount());  // 输出结果应为2000
        // 静态成员的同步示例
        for (int i = 0; i < 500; i++) {
            IntrinsicLockExample.incrementStatic();
        }
        System.out.println("Final static count: " + IntrinsicLockExample.staticCounter);  // 输出结果应为500
    }
}

详细解释:

三、显示锁(Lock)

 Java显示锁(显示锁通常指的是java.util.concurrent.locks.Lock接口及其实现类)提供了比内置锁(synchronized关键字)更强大和灵活的线程同步机制。显示锁允许程序员更加精确地控制线程的加锁和解锁行为,支持中断请求,以及非阻塞式的尝试获取锁等特性。

以下是使用java.util.concurrent.locks.ReentrantLock作为显示锁的一个示例代码:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class DisplayedLockExample {
    private final Lock lock = new ReentrantLock();
    private int counter;
    public void increment() {
        lock.lock(); // 加锁
        try {
            counter++;
        } finally {
            lock.unlock(); // 无论何时都要确保解锁
        }
    }
    public int getCount() {
        lock.lock(); // 同样需要加锁保护
        try {
            return counter;
        } finally {
            lock.unlock();
        }
    }
    // 显示锁还提供了更多的控制方式,比如尝试获取锁,支持中断等
    public void tryIncrementWithTimeout(int timeout, TimeUnit unit) throws InterruptedException {
        if (lock.tryLock(timeout, unit)) {
            try {
                counter++;
            } finally {
                lock.unlock();
            }
        } else {
            System.out.println("未能在规定时间内获取到锁");
        }
    }
}
// 使用示例
public class Main {
    public static void main(String[] args) {
        DisplayedLockExample example = new DisplayedLockExample();
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.increment();
            }
        });
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                example.increment();
            }
        });
        thread1.start();
        thread2.start();
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Final count: " + example.getCount());  // 输出结果应为2000
    }
}

详细解释:

显示锁相比内置锁的优势:

  1. 可以尝试非阻塞地获取锁,比如 tryLock() 和 tryLock(long timeout, TimeUnit unit) 方法。
  2. 支持中断,线程在等待锁时可以响应中断请求,这对于那些需要取消长时间等待的任务非常有用。
  3. 可以实现公平锁策略,即按照线程请求锁的顺序来分配锁,避免“饥饿”现象。
  4. 提供了锁的监听和唤醒机制,可通过 Condition 对象实现更复杂的同步结构

四、读写锁(Read-Write Locks)

 Java的读写锁(Read-Write Locks)是一种特殊的锁机制,它允许多个读取者同时访问共享资源,但在写入者访问时会排斥所有读取者和其他写入者。这使得在读多写少的情况下,系统的并发性能得到显著提升。Java中实现读写锁的主要类是java.util.concurrent.locks.ReadWriteLock,以及它的标准实现java.util.concurrent.locks.ReentrantReadWriteLock。

以下是一个使用ReentrantReadWriteLock的示例代码及其详细解释:

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockExample {
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private final Lock readLock = lock.readLock();
    private final Lock writeLock = lock.writeLock();
    private int sharedResource;
    public void read() {
        readLock.lock(); // 获取读锁
        try {
            // 多个线程可以同时在这里读取数据
            System.out.println("Reading the resource: " + sharedResource);
        } finally {
            readLock.unlock(); // 释放读锁
        }
    }
    public void update() {
        writeLock.lock(); // 获取写锁
        try {
            // 当有线程在执行这里的写操作时,其他所有读写线程都会被阻塞
            sharedResource++;
            System.out.println("Updated the resource to: " + sharedResource);
        } finally {
            writeLock.unlock(); // 释放写锁
        }
    }
    // 使用示例
    public static void main(String[] args) {
        ReadWriteLockExample example = new ReadWriteLockExample();
        ExecutorService executor = Executors.newFixedThreadPool(10);
        // 创建大量读取任务
        for (int i = 0; i < 20; i++) {
            executor.submit(() -> example.read());
        }
        // 创建少量写入任务
        for (int i = 0; i < 5; i++) {
            executor.submit(() -> example.update());
        }
        // 关闭线程池
        executor.shutdown();
    }
}

详细解释:

五、条件变量(Condition Objects)

 在Java中,条件变量是通过java.util.concurrent.locks.Condition接口实现的,它与锁(如ReentrantLock)一起使用,允许线程等待满足特定条件时被唤醒。下面是一个使用Condition对象的示例代码及详细解释:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionVariableExample {
    private final Lock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();
    // 共享资源,模拟队列为空的情况
    private boolean isEmpty = true;
    public void produce() {
        lock.lock();
        try {
            // 当队列为空时,生产者线程等待condition被signal
            while (!isEmpty) {
                condition.await();  // 线程在此处释放锁并进入等待状态
            }
            // 生产商品逻辑...
            System.out.println("Produced an item, queue is no longer empty.");
            isEmpty = false;  // 更新条件
            // 唤醒所有等待此condition的消费者线程
            condition.signalAll();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            lock.unlock();  // 不论如何都要确保解锁
        }
    }
    public void consume() {
        lock.lock();
        try {
            // 当队列非空时,消费者线程等待condition被signal
            while (isEmpty) {
                condition.await();  // 线程在此处释放锁并进入等待状态
            }
            // 消费商品逻辑...
            System.out.println("Consumed an item, queue is now empty.");
            isEmpty = true;  // 更新条件
            // 唤醒所有等待此condition的生产者线程
            condition.signalAll();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            lock.unlock();  // 不论如何都要确保解锁
        }
    }
    // 使用示例
    public static void main(String[] args) throws InterruptedException {
        ConditionVariableExample example = new ConditionVariableExample();
        Thread producer = new Thread(example::produce);
        Thread consumer = new Thread(example::consume);
        producer.start();
        consumer.start();
        // 确保生产者先启动并改变条件
        Thread.sleep(100);
        example.produce();  // 手动调用一次生产方法,以初始化条件变化
        producer.join();
        consumer.join();
    }
}

详细解释:

六、乐观锁(Optimistic Locking)

 乐观锁在Java中的实现通常依赖于原子变量类(如java.util.concurrent.atomic包下的类)或数据库事务中的版本号机制。乐观锁的假设是大多数情况下数据不会发生冲突,因此在修改数据前并不加锁,而是在更新时检查在此期间是否有其他线程修改过该数据。如果发现数据未被修改,则执行更新操作;否则则需要重新读取、验证并尝试更新。

以下是一个使用AtomicInteger作为乐观锁机制实现的简单示例:

import java.util.concurrent.atomic.AtomicInteger;
public class OptimisticLockingExample {
    private AtomicInteger counter = new AtomicInteger(0);
    public void increment() {
        // 读取当前值
        int currentValue;
        do {
            // 获取一个可能过时的值
            currentValue = counter.get();
            // 检查在此期间是否已经被其他线程修改过
        } while (!counter.compareAndSet(currentValue, currentValue + 1));  // 如果当前值未变,则更新为原值+1
        System.out.println("Counter incremented to: " + counter.get());
    }
    public static void main(String[] args) {
        OptimisticLockingExample example = new OptimisticLockingExample();
        Thread thread1 = new Thread(example::increment);
        Thread thread2 = new Thread(example::increment);
        thread1.start();
        thread2.start();
        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

详细解释:

七、悲观锁(Pessimistic Locking)

 悲观锁在Java中通常表现为获取到一个锁后,其他线程尝试访问该资源时会立即被阻塞,直到持有锁的线程释放锁。最直接的例子就是使用synchronized关键字修饰的方法或代码块。但为了更好地说明数据库层面的悲观锁实现,这里提供一个基于JDBC和Hibernate的示例:

JDBC示例(通过SQL的SELECT ... FOR UPDATE实现悲观锁):

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class PessimisticLockingExampleJDBC {
    private static final String URL = "jdbc:mysql://localhost:3306/mydb";
    private static final String USER = "root";
    private static final String PASSWORD = "password";
    public void updateDataWithPessimisticLock(int id) {
        try (Connection connection = DriverManager.getConnection(URL, USER, PASSWORD)) {
            connection.setAutoCommit(false); // 关闭自动提交,开始事务
            // 使用FOR UPDATE来获取悲观锁
            String sql = "SELECT * FROM my_table WHERE id = ? FOR UPDATE";
            PreparedStatement preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setInt(1, id);
            preparedStatement.execute();
            // 假设我们从查询结果中获取了数据,并准备更新它...
            // 更新操作
            String updateSql = "UPDATE my_table SET column = ? WHERE id = ?";
            preparedStatement = connection.prepareStatement(updateSql);
            preparedStatement.setString(1, "new_value");
            preparedStatement.setInt(2, id);
            preparedStatement.executeUpdate();
            connection.commit();  // 提交事务,释放锁
        } catch (SQLException e) {
            // 处理异常并回滚事务
            try {
                if (connection != null) {
                    connection.rollback();
                }
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
            e.printStackTrace();
        }
    }
}

Hibernate示例(通过Session的锁定方法实现悲观锁):

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class PessimisticLockingExampleHibernate {
    private SessionFactory sessionFactory;
    public PessimisticLockingExampleHibernate() {
        Configuration configuration = new Configuration().configure();
        sessionFactory = configuration.buildSessionFactory();
    }
    public void updateDataWithPessimisticLock(Integer id) {
        try (Session session = sessionFactory.openSession()) {
            // 开始事务
            session.beginTransaction();
            // 加载实体并显式地请求悲观锁
            MyEntity entity = session.get(MyEntity.class, id, LockMode.PESSIMISTIC_WRITE);
            // 假设我们在这里修改了entity的属性值...
            entity.setProperty("new_value");
            // 提交事务,同时释放锁
            session.getTransaction().commit();
        } catch (Exception e) {
            // 如果发生异常,则回滚事务
            if (session != null && session.getTransaction() != null && session.getTransaction().isActive()) {
                session.getTransaction().rollback();
            }
            e.printStackTrace();
        }
    }
}
@Entity
@Table(name = "my_table")
public class MyEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    // 其他属性及getter、setter省略...
}

详细解释:

八、自旋锁(Spin Locks)

 自旋锁在Java中主要用于解决线程间短时间的同步问题,尤其适用于等待时间极短并且CPU资源相对充足的场景。在Java中并没有直接提供名为“自旋锁”的API,但是可以通过循环和volatile关键字模拟实现自旋锁的行为。以下是一个简单的自旋锁示例:

public class SpinLock {
    private volatile boolean isLocked = false;
    public void lock() {
        while (true) {
            if (!isLocked) { // 当锁未被占用时
                if (compareAndSet(false, true)) { // 使用CAS操作尝试获取锁
                    break; // 成功获取锁后退出自旋
                }
            }
            // 锁被占用时,继续循环(自旋)
        }
    }
    public void unlock() {
        isLocked = false; // 释放锁
    }
    // 使用AtomicBoolean或Unsafe等工具类提供的原子操作来实现compareAndSet
    private boolean compareAndSet(boolean expect, boolean update) {
        return java.util.concurrent.atomic.AtomicBoolean.compareAndSet(this.isLocked, expect, update);
    }
    // 示例用法
    public static void main(String[] args) {
        final SpinLock spinLock = new SpinLock();
        Thread t1 = new Thread(() -> {
            spinLock.lock();
            try {
                System.out.println("Thread 1 acquired the lock");
                Thread.sleep(1000); // 模拟执行耗时任务
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                spinLock.unlock();
            }
        });
        Thread t2 = new Thread(() -> {
            spinLock.lock();
            try {
                System.out.println("Thread 2 acquired the lock");
            } finally {
                spinLock.unlock();
            }
        });
        t1.start();
        t2.start();
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

详细解释:

九、StampedLock

Java中的StampedLock是Java 8引入的一个高性能的并发工具类,它提供了更灵活的读写锁机制,包括悲观读锁、乐观读锁和写锁。每个锁操作都会返回一个戳记(stamp),后续的操作可以通过这个戳记来验证或释放锁。

以下是一个使用StampedLock实现读写锁的示例代码及详细解释:

import java.util.concurrent.locks.StampedLock;
public class StampedLockExample {
    private final StampedLock lock = new StampedLock();
    // 共享资源
    private int sharedResource;
    public void read() {
        long stamp = lock.readLock(); // 获取悲观读锁
        try {
            // 在此块中可以安全地读取sharedResource
            System.out.println("Reading the resource: " + sharedResource);
        } finally {
            lock.unlockRead(stamp); // 释放读锁
        }
    }
    public void optimisticRead() {
        long stamp = lock.tryOptimisticRead(); // 尝试获取乐观读锁
        int localCopy = sharedResource;
        if (lock.validate(stamp)) { // 验证在获取戳记后,数据是否被其他线程修改过
            // 如果没有被修改,则可以安全地使用localCopy
            System.out.println("Optimistically reading the resource: " + localCopy);
        } else {
            // 如果有被修改,需要升级到悲观读锁或者重新读取
            long newStamp = lock.readLock();
            try {
                // 现在可以安全地再次读取资源
                localCopy = sharedResource;
                System.out.println("Upgraded to悲观读锁, reading the resource: " + localCopy);
            } finally {
                lock.unlockRead(newStamp); // 释放悲观读锁
            }
        }
    }
    public void write(int newValue) {
        long stamp = lock.writeLock(); // 获取写锁
        try {
            // 在此块中可以安全地更新sharedResource
            sharedResource = newValue;
            System.out.println("Updated the resource to: " + sharedResource);
        } finally {
            lock.unlockWrite(stamp); // 释放写锁
        }
    }
    // 使用示例
    public static void main(String[] args) {
        StampedLockExample example = new StampedLockExample();
        // 创建并启动多个读取者和写入者线程...
    }
}

详细解释:

1.StampedLock提供三种模式的锁:

2.示例中read()方法展示了如何使用悲观读锁进行读操作,在读取期间阻止写操作。

3.optimisticRead()方法首先尝试乐观读锁,然后检查戳记的有效性。若数据未被更改,则可以直接使用本地缓存的值;否则,为了确保读取到最新数据,需要升级到悲观读锁。

4.write()方法展示了如何使用写锁执行写操作,在写入期间阻止所有其他读写操作。

总结

深入理解和熟练掌握Java中的锁机制是构建高效、稳定多线程程序的关键所在。开发者应依据具体的并发场景,权衡锁的开销与安全性,合理选择并应用合适的锁实现。

到此这篇关于Java锁机制详细讲解与实战应用的文章就介绍到这了,更多相关Java锁机制内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文