Java 多线程系列详解:深度解密线程同步与锁的奥秘

201 阅读5分钟

Java 多线程系列详解:深度解密线程同步与锁的奥秘

在多线程编程中,同步与锁机制是保障数据一致性与线程安全的关键技术。本文作为 Java 多线程系列的中篇,将深入探讨线程同步的重要性,并通过实例讲解 synchronized、显式锁(如 ReentrantLock)、读写锁(ReadWriteLock)、信号量(Semaphore)、栅栏(CyclicBarrier)等机制的使用,以及线程间通信的实现和对比。


一、线程同步的重要性

1. 什么是线程同步?

线程同步是一种协调多线程访问共享资源的技术,目的是避免线程间竞争导致的数据不一致问题。

2. 数据不一致问题的示例

以下代码模拟了多个线程对共享变量的访问,展示了数据不一致问题:

public class Counter {
    private int count = 0;
​
    public void increment() {
        // 让线程在这里故意延时,制造更多线程争夺的可能性
        int temp = count;
        try {
            Thread.sleep(1); // 模拟延时
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        count = temp + 1;
    }
​
    public int getCount() {
        return count;
    }
​
    public static void main(String[] args) {
        Counter counter = new Counter();
        Runnable task = () -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        };
​
        Thread t1 = new Thread(task);
        Thread t2 = new Thread(task);
        t1.start();
        t2.start();
​
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
​
        System.out.println("最终计数值: " + counter.getCount());
    }
}

输出结果可能是: 小于 2000,因为多个线程同时对 count 进行读写,导致了竞争条件。

为了解决这些问题,Java 提供了多种线程同步技术。


二、synchronized 关键字

synchronized 是 Java 中最基本的同步机制,用于确保同一时间只有一个线程可以访问同步代码块。

1. 使用方式

1.1 修饰实例方法

锁住当前对象实例。

public synchronized void increment() {
    count++;
}
1.2 修饰静态方法

锁住当前类的 Class 对象。

public static synchronized void staticIncrement() {
    staticCount++;
}
1.3 修饰代码块

锁住指定对象,灵活性更高。

public void increment() {
    synchronized (this) {
        count++;
    }
}

2. 示例:银行账户转账

以下代码模拟银行账户的多线程存取款操作,确保余额计算正确。

public class BankAccount {
    private double balance;
​
    public synchronized void deposit(double amount) {
        balance += amount;
        System.out.println(Thread.currentThread().getName() + " 存款 " + amount + ",当前余额: " + balance);
    }
​
    public synchronized void withdraw(double amount) {
        if (balance >= amount) {
            balance -= amount;
            System.out.println(Thread.currentThread().getName() + " 取款 " + amount + ",当前余额: " + balance);
        } else {
            System.out.println(Thread.currentThread().getName() + " 取款失败,余额不足!");
        }
    }
}

三、显式锁:ReentrantLock

ReentrantLock 是一个灵活的显式锁,提供了 synchronized 无法实现的高级功能。

1. 主要方法

  • lock():加锁。
  • unlock():释放锁。
  • tryLock():尝试获取锁,如果失败则立即返回。
  • lockInterruptibly():可响应中断的加锁。

2. 使用场景:停车场管理

以下代码模拟停车场管理系统,确保线程安全地操作停车位数量。

import java.util.concurrent.locks.ReentrantLock;
​
public class ParkingLot {
    private int availableSpaces = 3;
    private final ReentrantLock lock = new ReentrantLock();
​
    public void park() {
        lock.lock();
        try {
            if (availableSpaces > 0) {
                System.out.println(Thread.currentThread().getName() + " 停车成功,剩余车位: " + (--availableSpaces));
            } else {
                System.out.println(Thread.currentThread().getName() + " 停车失败,车位已满!");
            }
        } finally {
            lock.unlock();
        }
    }
}

四、读写锁(ReadWriteLock

读写锁将读写操作分离,允许多个线程同时读,而写操作是独占的。

1. 主要方法

  • readLock().lock():获取读锁。
  • writeLock().lock():获取写锁。

2. 使用场景:共享资源的读写分离

import java.util.concurrent.locks.ReentrantReadWriteLock;
​
public class ReadWriteExample {
    private int data = 0;
    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
​
    public void write(int value) {
        lock.writeLock().lock();
        try {
            data = value;
            System.out.println(Thread.currentThread().getName() + " 写入数据: " + data);
        } finally {
            lock.writeLock().unlock();
        }
    }
​
    public int read() {
        lock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + " 读取数据: " + data);
            return data;
        } finally {
            lock.readLock().unlock();
        }
    }
}

五、信号量(Semaphore

信号量用于控制同时访问特定资源的线程数量。

1. 主要方法

  • acquire():获取信号量。
  • release():释放信号量。

2. 使用场景:限制同时访问的线程数

import java.util.concurrent.Semaphore;
​
public class SemaphoreExample {
    private final Semaphore semaphore = new Semaphore(3);
​
    public void accessResource() {
        try {
            semaphore.acquire();
            System.out.println(Thread.currentThread().getName() + " 访问资源");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName() + " 释放资源");
            semaphore.release();
        }
    }
​
    public static void main(String[] args) {
        SemaphoreExample example = new SemaphoreExample();
        for (int i = 0; i < 10; i++) {
            new Thread(example::accessResource).start();
        }
    }
}

六、栅栏(CyclicBarrier

CyclicBarrier 用于让一组线程达到一个屏障点后再继续执行。

1. 主要方法

  • await():等待所有线程到达屏障点。

2. 使用场景:任务分阶段执行

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierExample {
    private final CyclicBarrier barrier = new CyclicBarrier(3, () -> {
        System.out.println("所有线程到达屏障点,继续执行");
    });

    public void performTask() {
        try {
            System.out.println(Thread.currentThread().getName() + " 到达屏障点");
            barrier.await();
            System.out.println(Thread.currentThread().getName() + " 开始执行任务");
        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        CyclicBarrierExample example = new CyclicBarrierExample();
        for (int i = 0; i < 3; i++) {
            new Thread(example::performTask).start();
        }
    }
}

七、倒计时闩(CountDownLatch

CountDownLatch 用于让一个线程等待其他线程完成操作。

1. 主要方法

  • countDown():递减计数器。
  • await():等待计数器归零。

2. 使用场景:多个子任务完成后汇总结果

import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
    private final CountDownLatch latch = new CountDownLatch(3);

    public void performTask() {
        System.out.println(Thread.currentThread().getName() + " 执行任务");
        latch.countDown();
    }

    public void waitForTasks() {
        try {
            latch.await();
            System.out.println("所有任务完成,主线程继续执行");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        CountDownLatchExample example = new CountDownLatchExample();
        for (int i = 0; i < 3; i++) {
            new Thread(example::performTask).start();
        }
        example.waitForTasks();
    }
}

八、总结

工具描述使用场景
synchronized基础同步机制保证共享资源的互斥访问
ReentrantLock显式锁,功能更灵活高级锁机制,支持可中断和超时功能
ReadWriteLock读写分离,提高并发性能多线程读取多,写入少的场景
Semaphore控制并发线程数量限制同时访问资源的线程数
CyclicBarrier同步点,所有线程到达后继续执行阶段性任务同步
CountDownLatch等待多个线程完成任务子任务完成后主线程继续执行

这些工具各有适用场景,可以根据实际需求选择合适的机制来实现线程同步和协调。