零基础学Java——第七章:多线程与并发编程(下)

第七章:多线程与并发编程

4. 线程池

在实际应用中,频繁创建和销毁线程会带来很大的系统开销。线程池通过复用线程来减少这种开销,提高系统性能。

4.1 线程池的基本概念

想象一个游泳池:与其每次想游泳时都挖一个新池子(创建新线程),不如建一个可以重复使用的游泳池(线程池)。人们可以来游泳(任务执行),游完后池子仍然存在,等待下一位游泳者。

线程池的优势:

  • 减少资源消耗:重复利用已创建的线程,减少线程创建和销毁的开销
  • 提高响应速度:任务到达时,无需等待线程创建就能立即执行
  • 提高线程的可管理性:统一管理线程,避免无限制创建线程导致系统崩溃

4.2 Java中的线程池

Java通过Executors工厂类提供了几种常用的线程池实现:

固定大小线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class FixedThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个固定大小为5的线程池
        ExecutorService executor = Executors.newFixedThreadPool(5);
        
        // 提交10个任务给线程池执行
        for (int i = 0; i < 10; i++) {
            final int taskId = i;
            executor.execute(() -> {
                System.out.println("任务" + taskId + "开始执行,线程名:" + Thread.currentThread().getName());
                try {
                    // 模拟任务执行
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任务" + taskId + "执行完毕");
            });
        }
        
        // 关闭线程池(不再接受新任务,等待所有任务完成后关闭)
        executor.shutdown();
    }
}
缓存线程池
public class CachedThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个缓存线程池(按需创建线程,空闲线程会被回收)
        ExecutorService executor = Executors.newCachedThreadPool();
        
        // 提交任务
        for (int i = 0; i < 20; i++) {
            final int taskId = i;
            executor.execute(() -> {
                System.out.println("任务" + taskId + "开始执行,线程名:" + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            
            // 模拟任务提交间隔
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        executor.shutdown();
    }
}
单线程执行器
public class SingleThreadExecutorExample {
    public static void main(String[] args) {
        // 创建一个单线程执行器(所有任务按提交顺序执行)
        ExecutorService executor = Executors.newSingleThreadExecutor();
        
        // 提交多个任务
        for (int i = 0; i < 5; i++) {
            final int taskId = i;
            executor.execute(() -> {
                System.out.println("任务" + taskId + "开始执行");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任务" + taskId + "执行完毕");
            });
        }
        
        executor.shutdown();
    }
}
定时任务线程池
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个定时任务线程池
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
        
        // 延迟3秒后执行任务
        executor.schedule(() -> {
            System.out.println("延迟任务执行");
        }, 3, TimeUnit.SECONDS);
        
        // 延迟1秒后,每2秒执行一次任务
        executor.scheduleAtFixedRate(() -> {
            System.out.println("周期任务执行,时间:" + System.currentTimeMillis()/1000);
        }, 1, 2, TimeUnit.SECONDS);
        
        // 注意:这里不调用shutdown,因为是周期性任务
    }
}

4.3 自定义线程池

通过ThreadPoolExecutor类可以创建自定义线程池,更精细地控制线程池的行为:

import java.util.concurrent.*;

public class CustomThreadPoolExample {
    public static void main(String[] args) {
        // 创建自定义线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
            2,                      // 核心线程数
            5,                      // 最大线程数
            60, TimeUnit.SECONDS,   // 空闲线程存活时间
            new ArrayBlockingQueue<>(10), // 工作队列
            Executors.defaultThreadFactory(), // 线程工厂
            new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
        );
        
        // 提交任务
        for (int i = 0; i < 15; i++) {
            final int taskId = i;
            executor.execute(() -> {
                System.out.println("任务" + taskId + "开始执行,线程名:" + Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        
        // 关闭线程池
        executor.shutdown();
    }
}

参数说明:

  • 核心线程数:线程池中保持活跃的线程数量
  • 最大线程数:线程池中允许的最大线程数
  • 空闲线程存活时间:超过核心线程数的空闲线程在被回收前的等待时间
  • 工作队列:存储等待执行的任务的队列
  • 线程工厂:创建新线程的工厂
  • 拒绝策略:当线程池和队列都满时的处理策略

4.4 线程池的生命周期

线程池有以下几种状态:

  • 运行(RUNNING):接受新任务并处理队列中的任务
  • 关闭(SHUTDOWN):不接受新任务,但处理队列中的任务
  • 停止(STOP):不接受新任务,不处理队列中的任务,中断正在执行的任务
  • 整理(TIDYING):所有任务已终止,工作线程数为0
  • 终止(TERMINATED):线程池已完全终止
// 关闭线程池(等待任务完成)
executor.shutdown();

// 立即关闭线程池(尝试停止所有正在执行的任务)
List<Runnable> unfinishedTasks = executor.shutdownNow();

// 等待线程池终止(最多等待5秒)
boolean terminated = executor.awaitTermination(5, TimeUnit.SECONDS);

4.5 线程池的实际应用

网页爬虫
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class WebCrawlerExample {
    public static void main(String[] args) {
        List<String> urlsToCrawl = new ArrayList<>();
        // 添加要爬取的URL
        urlsToCrawl.add("https://example.com/page1");
        urlsToCrawl.add("https://example.com/page2");
        // ... 添加更多URL
        
        // 创建线程池
        ExecutorService executor = Executors.newFixedThreadPool(5);
        
        // 提交爬取任务
        for (String url : urlsToCrawl) {
            executor.execute(() -> crawlPage(url));
        }
        
        // 关闭线程池
        executor.shutdown();
        try {
            // 等待所有任务完成,最多等待1小时
            executor.awaitTermination(1, TimeUnit.HOURS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        System.out.println("所有页面爬取完成");
    }
    
    private static void crawlPage(String url) {
        System.out.println("爬取页面:" + url + ",线程:" + Thread.currentThread().getName());
        // 模拟页面爬取过程
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("页面爬取完成:" + url);
    }
}
图片处理
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ImageProcessingExample {
    public static void main(String[] args) {
        String[] imageFiles = {"image1.jpg", "image2.jpg", "image3.jpg", "image4.jpg", "image5.jpg"};
        
        // 创建与CPU核心数相同的线程池
        int processors = Runtime.getRuntime().availableProcessors();
        ExecutorService executor = Executors.newFixedThreadPool(processors);
        
        // 提交图片处理任务
        for (String imageFile : imageFiles) {
            executor.execute(() -> processImage(imageFile));
        }
        
        executor.shutdown();
    }
    
    private static void processImage(String imageFile) {
        System.out.println("处理图片:" + imageFile + ",线程:" + Thread.currentThread().getName());
        // 模拟图片处理
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("图片处理完成:" + imageFile);
    }
}

5. 并发集合

Java提供了多种线程安全的集合类,用于在多线程环境下安全地操作数据。

5.1 并发集合概述

想象一个繁忙的餐厅:多个服务员(线程)同时查看和更新点菜单(集合)。如果使用普通的点菜单(非线程安全集合),可能会出现混乱;而使用专门设计的电子点菜系统(并发集合)则可以确保点单过程的正确性。

常见的并发集合类:

非线程安全集合对应的并发集合特点
ArrayListCopyOnWriteArrayList适用于读多写少的场景
HashSetCopyOnWriteArraySet基于CopyOnWriteArrayList实现
HashMapConcurrentHashMap分段锁实现,高并发性能好
QueueConcurrentLinkedQueue非阻塞队列
BlockingQueueArrayBlockingQueue等支持阻塞操作的队列

5.2 ConcurrentHashMap

ConcurrentHashMapHashMap的线程安全版本,采用分段锁技术,支持高并发访问。

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class ConcurrentHashMapExample {
    public static void main(String[] args) throws InterruptedException {
        // 对比HashMap和ConcurrentHashMap
        final Map<String, Integer> hashMap = new HashMap<>();
        final Map<String, Integer> concurrentMap = new ConcurrentHashMap<>();
        
        // 创建线程池
        ExecutorService executor = Executors.newFixedThreadPool(10);
        
        // 测试HashMap(可能会抛出ConcurrentModificationException)
        try {
            testMap(executor, hashMap, "HashMap");
        } catch (Exception e) {
            System.out.println("HashMap测试失败:" + e.getMessage());
        }
        
        // 测试ConcurrentHashMap
        testMap(executor, concurrentMap, "ConcurrentHashMap");
        
        executor.shutdown();
        executor.awaitTermination(10, TimeUnit.SECONDS);
        
        // 输出结果
        System.out.println("HashMap大小:" + hashMap.size());
        System.out.println("ConcurrentHashMap大小:" + concurrentMap.size());
    }
    
    private static void testMap(ExecutorService executor, final Map<String, Integer> map, String mapName) {
        // 10个线程同时写入
        for (int i = 0; i < 10; i++) {
            final int threadNum = i;
            executor.execute(() -> {
                for (int j = 0; j < 100; j++) {
                    map.put(mapName + "-" + threadNum + "-" + j, j);
                }
            });
        }
    }
}

5.3 CopyOnWriteArrayList

CopyOnWriteArrayListArrayList的线程安全版本,适用于读多写少的场景。

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class CopyOnWriteArrayListExample {
    public static void main(String[] args) {
        // 创建普通ArrayList和CopyOnWriteArrayList
        List<String> normalList = new ArrayList<>();
        List<String> copyOnWriteList = new CopyOnWriteArrayList<>();
        
        // 添加元素
        for (int i = 0; i < 10; i++) {
            normalList.add("Item " + i);
            copyOnWriteList.add("Item " + i);
        }
        
        // 使用迭代器遍历并尝试修改列表
        System.out.println("=== ArrayList测试 ===");
        try {
            Iterator<String> iterator = normalList.iterator();
            while (iterator.hasNext()) {
                String item = iterator.next();
                System.out.println(item);
                if (item.equals("Item 5")) {
                    // 这会抛出ConcurrentModificationException
                    normalList.remove(item);
                }
            }
        } catch (Exception e) {
            System.out.println("发生异常:" + e.getMessage());
        }
        
        System.out.println("\n=== CopyOnWriteArrayList测试 ===");
        Iterator<String> iterator = copyOnWriteList.iterator();
        while (iterator.hasNext()) {
            String item = iterator.next();
            System.out.println(item);
            if (item.equals("Item 5")) {
                // 这不会抛出异常,但迭代器不会看到修改后的列表
                copyOnWriteList.remove(item);
                System.out.println("已移除Item 5,但迭代器仍然可以看到它");
            }
        }
        
        System.out.println("\nCopyOnWriteArrayList当前内容:");
        for (String item : copyOnWriteList) {
            System.out.println(item);
        }
    }
}

5.4 阻塞队列

阻塞队列是支持两个附加操作的队列:

  • 当队列为空时,获取元素的线程会等待队列变为非空
  • 当队列满时,存储元素的线程会等待队列可用
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockingQueueExample {
    public static void main(String[] args) {
        // 创建容量为3的阻塞队列
        BlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
        
        // 生产者线程
        Thread producer = new Thread(() -> {
            try {
                System.out.println("生产者开始生产数据");
                for (int i = 0; i < 6; i++) {
                    String data = "Data-" + i;
                    System.out.println("生产数据:" + data);
                    queue.put(data); // 如果队列满,会阻塞
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        
        // 消费者线程
        Thread consumer = new Thread(() -> {
            try {
                System.out.println("消费者开始消费数据");
                while (true) {
                    // 模拟消费者处理速度较慢
                    Thread.sleep(2000);
                    String data = queue.take(); // 如果队列空,会阻塞
                    System.out.println("消费数据:" + data);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        
        producer.start();
        consumer.start();
    }
}

5.5 并发集合的选择

选择合适的并发集合需要考虑以下因素:

  1. 读写比例:读多写少选择CopyOnWriteArrayList,读写均衡选择ConcurrentHashMap
  2. 是否需要阻塞操作:需要阻塞选择BlockingQueue,不需要选择ConcurrentLinkedQueue
  3. 性能要求:高性能要求选择非阻塞集合,如ConcurrentHashMap
  4. 数据一致性要求:强一致性要求可能需要额外的同步措施

6. 原子类

原子类是Java提供的一组支持原子操作的类,可以在不使用锁的情况下实现线程安全。

6.1 原子类基本概念

想象一个计数器:多个人同时操作一个机械计数器,每人增加一次。如果大家同时按下按钮,计数器可能只增加一次而不是多次。原子类就像一个特殊设计的计数器,确保每次按下都会被准确记录,不会丢失。

原子操作是指不会被线程调度机制中断的操作,这种操作一旦开始,就一定会执行完毕。

6.2 常用原子类

Java提供了多种原子类:

  1. 基本类型原子类

    • AtomicInteger:整型的原子类
    • AtomicLong:长整型的原子类
    • AtomicBoolean:布尔型的原子类
  2. 数组原子类

    • AtomicIntegerArray:整型数组原子类
    • AtomicLongArray:长整型数组原子类
    • AtomicReferenceArray:引用类型数组原子类
  3. 引用原子类

    • AtomicReference:引用类型原子类
    • AtomicStampedReference:带有版本号的引用类型原子类
    • AtomicMarkableReference:带有标记的引用类型原子类
  4. 字段更新器

    • AtomicIntegerFieldUpdater:整型字段的原子更新器
    • AtomicLongFieldUpdater:长整型字段的原子更新器
    • AtomicReferenceFieldUpdater:引用类型字段的原子更新器

6.3 AtomicInteger示例

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class AtomicIntegerExample {
    // 普通整数计数器(非线程安全)
    private static int normalCounter = 0;
    // 原子整数计数器(线程安全)
    private static AtomicInteger atomicCounter = new AtomicInteger(0);
    
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executor = Executors.newFixedThreadPool(10);
        
        // 10个线程同时执行
        for (int i = 0; i < 10; i++) {
            executor.execute(() -> {
                for (int j = 0; j < 1000; j++) {
                    // 普通计数器递增
                    normalCounter++;
                    // 原子计数器递增
                    atomicCounter.incrementAndGet();
                }
            });
        }
        
        executor.shutdown();
        executor.awaitTermination(10, TimeUnit.SECONDS);
        
        System.out.println("普通计数器最终值:" + normalCounter);
        System.out.println("原子计数器最终值:" + atomicCounter.get());
    }
}

6.4 CAS操作

原子类的核心是**比较并交换(Compare And Swap, CAS)**操作,这是一种无锁算法,用于在多线程环境下安全地修改共享变量。

CAS操作包含三个操作数:

  • 内存位置(V):要更新的变量
  • 预期原值(A):更新前的预期值
  • 新值(B):要设置的新值

CAS的工作原理:只有当V的当前值等于A时,才会将V的值更新为B,否则不会执行更新操作。整个比较和更新操作是原子的。

import java.util.concurrent.atomic.AtomicInteger;

public class CASExample {
    public static void main(String[] args) {
        AtomicInteger atomicInt = new AtomicInteger(100);
        
        // 使用CAS操作更新值
        boolean success = atomicInt.compareAndSet(100, 200);
        System.out.println("CAS操作结果:" + success + ",当前值:" + atomicInt.get());
        
        // 再次尝试CAS操作(预期值不匹配,将失败)
        success = atomicInt.compareAndSet(100, 300);
        System.out.println("CAS操作结果:" + success + ",当前值:" + atomicInt.get());
        
        // 获取当前值并增加
        int oldValue = atomicInt.getAndAdd(50);
        System.out.println("增加前的值:" + oldValue + ",增加后的值:" + atomicInt.get());
        
        // 增加并获取新值
        int newValue = atomicInt.addAndGet(50);
        System.out.println("增加后的新值:" + newValue);
    }
}

6.5 原子引用

AtomicReference可以让你原子性地更新引用类型:

import java.util.concurrent.atomic.AtomicReference;

public class AtomicReferenceExample {
    static class User {
        private String name;
        private int age;
        
        public User(String name, int age) {
            this.name = name;
            this.age = age;
        }
        
        @Override
        public String toString() {
            return "User{name='" + name + "', age=" + age + "}";
        }
    }
    
    public static void main(String[] args) {
        // 创建原子引用
        AtomicReference<User> userRef = new AtomicReference<>(new User("Tom", 20));
        
        // 获取当前引用
        User oldUser = userRef.get();
        System.out.println("原始用户:" + oldUser);
        
        // 创建新用户
        User newUser = new User("Jerry", 25);
        
        // 原子性地更新引用
        boolean success = userRef.compareAndSet(oldUser, newUser);
        System.out.println("更新结果:" + success + ",当前用户:" + userRef.get());
    }
}

6.6 ABA问题与解决方案

ABA问题是CAS操作的一个常见问题:如果一个值原来是A,变成了B,又变回了A,使用CAS检查时会认为它没有变化,但实际上已经发生了变化。

AtomicStampedReference通过引入版本号解决ABA问题:

import java.util.concurrent.atomic.AtomicStampedReference;

public class ABAExample {
    public static void main(String[] args) throws InterruptedException {
        // 初始值为100,初始版本为1
        AtomicStampedReference<Integer> atomicStampedRef = new AtomicStampedReference<>(100, 1);
        
        // 获取当前版本号
        int initialStamp = atomicStampedRef.getStamp();
        Integer initialValue = atomicStampedRef.getReference();
        System.out.println("初始值:" + initialValue + ",初始版本号:" + initialStamp);
        
        // 线程1:将100 -> 101 -> 100,模拟ABA问题
        Thread thread1 = new Thread(() -> {
            // 先更新为101
            atomicStampedRef.compareAndSet(100, 101, initialStamp, initialStamp + 1);
            int stamp = atomicStampedRef.getStamp();
            // 再更新回100,但版本号已经改变
            atomicStampedRef.compareAndSet(101, 100, stamp, stamp + 1);
            System.out.println("线程1完成ABA操作,当前值:" + atomicStampedRef.getReference() + 
                             ",当前版本号:" + atomicStampedRef.getStamp());
        });
        
        // 线程2:期望更新100 -> 200,但会检查版本号
        Thread thread2 = new Thread(() -> {
            try {
                // 让线程1先执行完ABA操作
                Thread.sleep(1000);
                
                // 尝试更新,使用初始版本号
                boolean success = atomicStampedRef.compareAndSet(100, 200, initialStamp, initialStamp + 1);
                System.out.println("线程2更新结果:" + success + ",当前值:" + atomicStampedRef.getReference() + 
                                 ",当前版本号:" + atomicStampedRef.getStamp());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        
        thread1.start();
        thread2.start();
        
        thread1.join();
        thread2.join();
    }
}

7. 并发工具类

Java提供了一系列并发工具类,用于解决多线程编程中的常见问题,如线程协作、同步控制等。

7.1 CountDownLatch

CountDownLatch是一个同步辅助类,允许一个或多个线程等待,直到其他线程完成一组操作。

想象一场赛跑:裁判需要等待所有运动员(线程)都到达起跑线后才能发令开始。CountDownLatch就像裁判手中的计数器,每个运动员到位后计数减一,当所有人都准备好(计数为零)时,比赛开始。

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CountDownLatchExample {
    public static void main(String[] args) throws InterruptedException {
        // 创建一个初始计数为5的CountDownLatch
        CountDownLatch latch = new CountDownLatch(5);
        
        // 创建线程池
        ExecutorService executor = Executors.newFixedThreadPool(5);
        
        System.out.println("主线程开始等待所有工作线程完成...");
        
        // 提交5个任务
        for (int i = 0; i < 5; i++) {
            final int taskId = i + 1;
            executor.execute(() -> {
                try {
                    // 模拟任务执行
                    System.out.println("任务" + taskId + "开始执行");
                    Thread.sleep((long) (Math.random() * 3000));
                    System.out.println("任务" + taskId + "执行完毕");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // 任务完成,计数减一
                    latch.countDown();
                }
            });
        }
        
        // 等待所有任务完成
        latch.await();
        System.out.println("所有任务已完成,主线程继续执行");
        
        executor.shutdown();
    }
}

7.2 CyclicBarrier

CyclicBarrier是一个同步辅助类,允许一组线程互相等待,直到所有线程都到达一个公共屏障点。

想象一次团队旅行:大家约定在某个地点集合后再一起出发。CyclicBarrier就像这个集合点,每个人(线程)到达后就等待,直到所有人都到齐,然后一起前进。

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

public class CyclicBarrierExample {
    public static void main(String[] args) {
        // 创建一个CyclicBarrier,等待4个线程,并在所有线程到达屏障时执行一个任务
        CyclicBarrier barrier = new CyclicBarrier(4, () -> {
            System.out.println("所有线程已到达屏障点,执行屏障操作");
        });
        
        // 创建4个线程
        for (int i = 0; i < 4; i++) {
            final int threadId = i + 1;
            new Thread(() -> {
                try {
                    System.out.println("线程" + threadId + "开始执行");
                    Thread.sleep((long) (Math.random() * 3000));
                    System.out.println("线程" + threadId + "到达屏障点,等待其他线程");
                    
                    // 等待其他线程到达屏障点
                    barrier.await();
                    
                    System.out.println("线程" + threadId + "继续执行");
                } catch (InterruptedException | BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

7.3 Semaphore

Semaphore是一个计数信号量,用于控制同时访问特定资源的线程数量。

想象一个有限停车场:只有特定数量的停车位,车辆(线程)需要获取许可(信号量)才能进入,离开时释放许可给其他车辆使用。

import java.util.concurrent.Semaphore;

public class SemaphoreExample {
    public static void main(String[] args) {
        // 创建一个只有3个许可的信号量(模拟3个停车位)
        Semaphore semaphore = new Semaphore(3);
        
        // 创建10辆车(10个线程)
        for (int i = 0; i < 10; i++) {
            final int carId = i + 1;
            new Thread(() -> {
                try {
                    System.out.println("汽车" + carId + "等待进入停车场");
                    
                    // 获取许可
                    semaphore.acquire();
                    
                    System.out.println("汽车" + carId + "进入停车场,当前可用停车位:" + 
                                     (3 - semaphore.availablePermits()));
                    
                    // 模拟停车时间
                    Thread.sleep((long) (Math.random() * 10000));
                    
                    // 释放许可
                    semaphore.release();
                    System.out.println("汽车" + carId + "离开停车场,当前可用停车位:" + 
                                     (3 - semaphore.availablePermits()));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

7.4 Exchanger

Exchanger是一个同步点,允许两个线程在这个点交换数据。

想象两个朋友交换礼物:两人必须同时到达约定地点才能完成交换。Exchanger就像这个约定地点,两个线程都到达后才能交换数据并继续执行。

import java.util.concurrent.Exchanger;

public class ExchangerExample {
    public static void main(String[] args) {
        // 创建Exchanger对象
        Exchanger<String> exchanger = new Exchanger<>();
        
        // 线程A
        new Thread(() -> {
            try {
                String dataA = "来自线程A的数据";
                System.out.println("线程A准备交换数据:" + dataA);
                
                // 等待与线程B交换数据
                String dataFromB = exchanger.exchange(dataA);
                
                System.out.println("线程A收到数据:" + dataFromB);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        
        // 线程B
        new Thread(() -> {
            try {
                // 模拟线程B晚一点到达交换点
                Thread.sleep(3000);
                
                String dataB = "来自线程B的数据";
                System.out.println("线程B准备交换数据:" + dataB);
                
                // 等待与线程A交换数据
                String dataFromA = exchanger.exchange(dataB);
                
                System.out.println("线程B收到数据:" + dataFromA);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

7.5 Phaser

Phaser是一个更灵活的同步屏障,允许执行多阶段任务,每个阶段结束时同步所有参与者。

想象一场多阶段比赛:所有选手必须完成当前阶段才能一起进入下一阶段。Phaser就像赛事组织者,确保所有选手同步推进。

import java.util.concurrent.Phaser;

public class PhaserExample {
    public static void main(String[] args) {
        // 创建Phaser,初始参与者数量为4
        Phaser phaser = new Phaser(4);
        
        // 创建4个线程
        for (int i = 0; i < 4; i++) {
            final int threadId = i + 1;
            new Thread(() -> {
                // 第一阶段
                System.out.println("线程" + threadId + "开始执行第一阶段");
                sleepRandomTime();
                System.out.println("线程" + threadId + "完成第一阶段,等待其他线程");
                phaser.arriveAndAwaitAdvance(); // 等待所有线程完成第一阶段
                
                // 第二阶段
                System.out.println("线程" + threadId + "开始执行第二阶段");
                sleepRandomTime();
                System.out.println("线程" + threadId + "完成第二阶段,等待其他线程");
                phaser.arriveAndAwaitAdvance(); // 等待所有线程完成第二阶段
                
                // 第三阶段
                System.out.println("线程" + threadId + "开始执行第三阶段");
                sleepRandomTime();
                System.out.println("线程" + threadId + "完成第三阶段");
                phaser.arriveAndDeregister(); // 完成并取消注册
            }).start();
        }
    }
    
    private static void sleepRandomTime() {
        try {
            Thread.sleep((long) (Math.random() * 2000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

7.6 CompletableFuture

CompletableFuture是Java 8引入的一个类,提供了强大的异步编程能力,支持组合、链式调用和异常处理。

想象一个复杂的任务流程:你需要先获取数据,然后处理数据,最后展示结果。CompletableFuture允许你以非阻塞的方式定义这些步骤,并处理它们之间的依赖关系。

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

public class CompletableFutureExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建一个异步任务
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            try {
                System.out.println("异步任务开始执行,线程:" + Thread.currentThread().getName());
                TimeUnit.SECONDS.sleep(2);
                return "任务结果";
            } catch (InterruptedException e) {
                throw new IllegalStateException(e);
            }
        });
        
        // 添加回调函数
        future.thenAccept(result -> {
            System.out.println("处理结果:" + result + ",线程:" + Thread.currentThread().getName());
        });
        
        System.out.println("主线程继续执行其他操作");
        
        // 等待异步任务完成
        future.get();
        
        // 组合多个CompletableFuture
        CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                return "结果1";
            } catch (InterruptedException e) {
                throw new IllegalStateException(e);
            }
        });
        
        CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
                return "结果2";
            } catch (InterruptedException e) {
                throw new IllegalStateException(e);
            }
        });
        
        // 组合两个异步任务的结果
        CompletableFuture<String> combinedFuture = future1.thenCombine(future2, (result1, result2) -> {
            return result1 + " + " + result2;
        });
        
        System.out.println("组合结果:" + combinedFuture.get());
        
        // 异常处理
        CompletableFuture<String> failedFuture = CompletableFuture.supplyAsync(() -> {
            if (true) throw new RuntimeException("故意抛出异常");
            return "不会返回的结果";
        }).exceptionally(ex -> {
            System.out.println("捕获到异常:" + ex.getMessage());
            return "默认结果";
        });
        
        System.out.println("异常处理后的结果:" + failedFuture.get());
    }
}

7.7 并发工具类的选择

选择合适的并发工具类需要考虑以下因素:

  1. 线程协作模式

    • 一个线程等待多个线程完成:CountDownLatch
    • 多个线程相互等待:CyclicBarrierPhaser
    • 控制并发访问数量:Semaphore
    • 两个线程交换数据:Exchanger
  2. 任务类型

    • 异步任务处理:CompletableFuture
    • 多阶段任务:Phaser
  3. 重用性

    • 需要重复使用:CyclicBarrierSemaphorePhaser
    • 一次性使用:CountDownLatch

总结

本章补充内容介绍了Java并发编程中的重要组件:线程池、并发集合、原子类和并发工具类。这些工具和类库为开发高效、安全的多线程应用提供了强大支持。

  • 线程池:通过复用线程减少创建和销毁线程的开销,提高系统性能
  • 并发集合:提供线程安全的集合类,避免在多线程环境下使用同步代码块
  • 原子类:提供原子操作,无需使用锁就能保证线程安全
  • 并发工具类:提供多种线程协作工具,简化复杂的多线程交互

掌握这些工具和技术,将使你能够开发出高效、稳定的并发应用程序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值