Java之中的并发同步工具类

1. 同步机制:保证线程安全

多个线程同时访问共享资源时,如果没有正确的同步机制,可能会导致数据不一致或竞争条件(race condition)。为了避免这些问题,Java 提供了以下几种同步机制:

1.1. 使用 synchronized 关键字
synchronized 是 Java 中最基础的同步机制,主要用于防止多个线程同时访问共享资源。可以使用 synchronized 来修饰方法或者代码块。

修饰实例方法:表示当前对象的所有实例方法都使用同一个锁。
修饰静态方法:表示当前类的所有静态方法使用同一个锁。
修饰代码块:可以指定特定的资源作为锁,从而实现更细粒度的控制。

class Counter {
    private int count = 0;

    // 使用 synchronized 修饰方法,保证线程安全
    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}

public class SynchronizedExample {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();

        // 创建多个线程来测试线程安全
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();

        System.out.println("最终计数:" + counter.getCount()); // 输出 2000
    }
}

解析:

synchronized:通过使用 synchronized 保证同一时间只有一个线程可以访问 increment() 和 getCount() 方法,从而避免了竞态条件。
1.2. 使用 ReentrantLock
ReentrantLock 是 java.util.concurrent.locks 包下的一个类,它比 synchronized 提供了更灵活的锁机制。ReentrantLock 允许线程在访问共享资源时显式地获取和释放锁,它支持可中断的锁请求、定时锁请求等特性。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Counter {
    private int count = 0;
    private Lock lock = new ReentrantLock();

    // 使用 ReentrantLock 显式获取和释放锁
    public void increment() {
        lock.lock();  // 获取锁
        try {
            count++;
        } finally {
            lock.unlock();  // 确保释放锁
        }
    }

    public int getCount() {
        return count;
    }
}

public class LockExample {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();

        // 创建多个线程来测试线程安全
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();

        System.out.println("最终计数:" + counter.getCount()); // 输出 2000
    }
}

解析:

  • ReentrantLock:与 synchronized 不同,ReentrantLock 允许线程在获取锁后能做更多的控制,如可以通过 lock.tryLock() 来尝试获取锁,或者设置超时等。

 2. 并发工具类

Java 提供了许多并发工具类,这些类帮助我们在复杂的并发环境下,协调线程的执行顺序,简化并发编程的难度。

2.1. CountDownLatch
CountDownLatch 是一种同步工具类,它允许一个或多个线程等待直到在其他线程中执行的一组操作完成。CountDownLatch 维护一个计数器,每当一个线程完成某个任务时,计数器就会减 1,直到计数器减到 0,等待的线程才能继续执行。

import java.util.concurrent.CountDownLatch;

public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        int taskCount = 3;
        CountDownLatch latch = new CountDownLatch(taskCount);

        // 创建并启动 3 个线程
        for (int i = 0; i < taskCount; i++) {
            final int taskId = i + 1;
            new Thread(() -> {
                System.out.println("任务 " + taskId + " 正在执行...");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                latch.countDown();  // 任务完成,计数器减 1
                System.out.println("任务 " + taskId + " 完成");
            }).start();
        }

        latch.await();  // 等待直到计数器为 0
        System.out.println("所有任务完成,继续执行主线程");
    }
}

解析:

CountDownLatch:在上述示例中,主线程等待所有子线程完成工作后再继续执行。latch.countDown() 将计数器减 1,latch.await() 会阻塞主线程直到计数器为 0。
2.2. CyclicBarrier
CyclicBarrier 类允许一组线程相互等待,直到所有线程都到达某个公共屏障点。CyclicBarrier 可以重复使用,因此适用于需要反复等待一组线程到达某个点的场景。

import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierExample {
    public static void main(String[] args) throws InterruptedException {
        int parties = 3;  // 线程数量
        CyclicBarrier barrier = new CyclicBarrier(parties, () -> {
            System.out.println("所有线程到达屏障,执行一些操作...");
        });

        // 启动多个线程
        for (int i = 0; i < parties; i++) {
            final int threadId = i + 1;
            new Thread(() -> {
                System.out.println("线程 " + threadId + " 到达屏障");
                try {
                    barrier.await();  // 等待其他线程到达屏障
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println("线程 " + threadId + " 继续执行");
            }).start();
        }
    }
}

解析:

CyclicBarrier:每个线程在 barrier.await() 处阻塞,直到所有线程都到达屏障,屏障才会被解除,并执行可选的 Runnable 操作。
2.3. Semaphore
Semaphore 是一种计数信号量,它用来控制同时访问特定资源的线程数量。可以用来限制并发线程的数量,适用于资源共享的场景。

import java.util.concurrent.Semaphore;

public class SemaphoreExample {
    public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore = new Semaphore(2);  // 限制最多 2 个线程同时访问

        // 启动多个线程
        for (int i = 0; i < 5; i++) {
            final int threadId = i + 1;
            new Thread(() -> {
                try {
                    semaphore.acquire();  // 获取许可
                    System.out.println("线程 " + threadId + " 正在执行...");
                    Thread.sleep(1000);  // 模拟任务执行
                    System.out.println("线程 " + threadId + " 执行完毕");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();  // 释放许可
                }
            }).start();
        }
    }
}

解析:

Semaphore:在这个例子中,Semaphore 被初始化为 2,意味着最多允许 2 个线程同时访问受限资源。每个线程在执行任务前调用 acquire() 方法来获取许可,执行完任务后调用 release() 来释放许可。
当有多个线程尝试访问时,acquire() 会使线程阻塞,直到有空余的许可,只有当许可被释放时,其他线程才可以获得许可并继续执行。

3. 其他并发工具类简介

除了 CountDownLatch、CyclicBarrier 和 Semaphore,Java 还提供了许多其他的并发工具类,能够帮助开发者解决不同的并发编程问题。

Exchanger:Exchanger 是一个简单的同步点,两个线程通过 exchange() 方法交换数据。它适用于需要两个线程互相交换数据的场景。

import java.util.concurrent.Exchanger;

public class ExchangerExample {
    public static void main(String[] args) {
        Exchanger exchanger = new Exchanger<>();

        // 线程 1
        new Thread(() -> {
            try {
                String message = "来自线程1的消息";
                System.out.println("线程1: 交换消息 -> " + message);
                message = exchanger.exchange(message);  // 交换数据
                System.out.println("线程1: 接收到消息 -> " + message);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        // 线程 2
        new Thread(() -> {
            try {
                String message = "来自线程2的消息";
                System.out.println("线程2: 交换消息 -> " + message);
                message = exchanger.exchange(message);  // 交换数据
                System.out.println("线程2: 接收到消息 -> " + message);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

解析:

Exchanger:这个工具类允许两个线程交换数据。在这个例子中,线程 1 和线程 2 通过 exchange() 方法交换字符串信息,exchange() 会阻塞线程,直到两个线程都准备好交换数据。
Phaser:Phaser 是一种更灵活的线程同步工具,类似于 CountDownLatch 和 CyclicBarrier,但是支持更多的功能,例如可以动态调整参与的线程数。

4. 总结

在今天的学习中,我们深入探讨了 Java 中的几种同步与并发工具类:

同步机制:通过 synchronized 关键字和 ReentrantLock 来保证线程安全。
并发工具类:
CountDownLatch 用于控制线程等待某些操作完成后再继续执行。
CyclicBarrier 用于协调多个线程在同一时刻继续执行,适合处理一组线程的协作。
Semaphore 用于限制并发线程数,适合处理有限资源的并发访问。
Exchanger 用于两个线程之间交换数据,适合需要线程间通信的场景。

你可能感兴趣的:(java,开发语言)