欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 科技 > IT业 > 《Effective Java》学习笔记——第7部分并发

《Effective Java》学习笔记——第7部分并发

2025/4/21 6:47:19 来源:https://blog.csdn.net/weixin_37693760/article/details/145347542  浏览:    关键词:《Effective Java》学习笔记——第7部分并发

文章目录

      • 一、前言
      • 二、并发最佳实践
        • 1. 优先使用现有的并发库
        • 2. 避免共享可变数据
        • 3. 最小化锁的持有时间
        • 4. 使用合适的同步策略
        • 5. 使用 volatile 变量来避免缓存问题
        • 6.避免死锁
        • 7. 使用 ExecutorService管理线程
        • 8. 优先使用无锁并发工具
      • 三、小结


image-20250122103431384

一、前言

《Effective Java》第7部分“并发”介绍了如何编写高效、安全的多线程程序。随着多核处理器的普及,Java 的并发编程变得更加重要。本部分的内容涵盖了并发编程中的一些常见问题,并提供了一些最佳实践和技巧,以帮助开发编写出更加可靠且高效的并发程序。

二、并发最佳实践

1. 优先使用现有的并发库
  • 原因:Java 提供了强大的并发工具库(如 java.util.concurrent 包),这些库经过高度优化,可以减少开发人员的工作量并避免常见的并发错误。手动实现并发机制可能会导致复杂且容易出错的代码。

  • 最佳实践:

    • 使用 Executor 框架来管理线程池,避免手动创建线程。
    • 使用 CountDownLatchCyclicBarrierSemaphore 等并发工具类来协调线程之间的同步。
    • 使用 ConcurrentHashMapCopyOnWriteArrayList 等线程安全的集合类来替代传统的集合类。
  • 示例:

    // 使用 Executor 服务来管理线程
    ExecutorService executor = Executors.newFixedThreadPool(10);
    executor.submit(() -> {// 执行并发任务
    });
    executor.shutdown();
    
2. 避免共享可变数据
  • 原因:多个线程同时访问和修改共享数据时,可能会导致数据的不一致性、竞态条件和死锁等问题。为了确保线程安全,必须谨慎处理共享数据。

  • 最佳实践:

    • 尽量避免在多个线程之间共享可变的数据。如果需要共享数据,考虑使用同步机制(如 synchronized)或 java.util.concurrent 包中的并发工具类。
    • 尽量使用不可变对象(immutable)和局部变量,这些变量的状态无法被其他线程修改,减少了并发问题。
  • 示例:

    // 使用 synchronized 来保护共享数据
    private final Object lock = new Object();
    private int sharedData;public void increment() {synchronized (lock) {sharedData++;}
    }
    
3. 最小化锁的持有时间
  • 原因:锁是并发编程中的一个重要机制,但它也会影响程序的性能。长时间持有锁会导致其他线程无法访问资源,降低程序的并发性和性能。

  • 最佳实践:

    • 将锁的持有时间限制在最小范围内,尽量避免在锁定区域内执行耗时操作。
    • 尽可能使用更细粒度的锁(如锁定某一方法而不是整个类)。
  • 示例:

    // 锁的持有时间较长(不推荐)
    public void process() {synchronized (lock) {// 执行一些耗时操作performTimeConsumingTask();}
    }// 推荐:减少锁的持有时间
    public void process() {performTimeConsumingTask();  // 不在同步块中执行耗时操作synchronized (lock) {// 执行与共享数据相关的操作updateSharedData();}
    }
    
4. 使用合适的同步策略
  • 原因:在并发编程中,不同的任务需要不同的同步策略。错误的同步策略可能导致性能问题、死锁或其他并发错误。

  • 最佳实践:

    • 对于只读操作,避免加锁,因为它们不会修改共享数据。
    • 对于必须同步的任务,使用 synchronizedReentrantLock 或其他并发工具类来确保线程安全。
    • 对于短时间的锁操作,可以考虑使用 ReentrantLock,它比 synchronized 更加灵活。
  • 示例:

    // 使用 synchronized 进行同步(简单场景)
    public synchronized void increment() {sharedCounter++;
    }// 使用 ReentrantLock 进行同步(更灵活的场景)
    private final ReentrantLock lock = new ReentrantLock();public void increment() {lock.lock();try {sharedCounter++;} finally {lock.unlock();}
    }
    
5. 使用 volatile 变量来避免缓存问题
  • 原因:在多线程环境下,线程之间的共享数据可能会被保存在各自的 CPU 缓存中,导致不同线程读取到不同的数据副本。volatile 关键字可以确保数据的一致性。

  • 最佳实践:

    • 对于简单的共享变量,使用 volatile 关键字来确保它们在多个线程之间的可见性。
    • 需要注意,volatile 不能保证复合操作的原子性(如 i++),因此需要配合其他同步机制。
  • 示例:

    private volatile boolean flag = false;public void toggleFlag() {flag = !flag;  // 保证线程间对 flag 的一致性
    }
    
6.避免死锁
  • 原因:死锁发生在两个或多个线程相互等待对方释放资源时,导致程序无法继续执行。死锁是并发编程中常见且棘手的问题。

  • 最佳实践:

    • 使用合适的锁顺序来避免死锁。如果多个线程需要获取多个锁,确保它们按照相同的顺序获取锁。
    • 使用 tryLock() 等方法来避免死锁,尝试在有限时间内获取锁,避免长期阻塞。
  • 示例:

    // 死锁的示例
    synchronized (lock1) {synchronized (lock2) {// 操作}
    }synchronized (lock2) {synchronized (lock1) {// 操作}
    }// 推荐:避免死锁
    synchronized (lock1) {synchronized (lock2) {// 操作}
    }
    
7. 使用 ExecutorService管理线程
  • 原因:直接使用 Thread 来创建和管理线程是一种低效且容易出错的方法。ExecutorService 提供了一个更高效、更灵活的线程池管理机制。

  • 最佳实践:

    • 使用 ExecutorService 来管理线程池,自动调度和重用线程,而不是每次都创建新的线程。
    • 使用 submit() 提交任务,使用 Future 获取任务结果。
    • 优先使用 Executors 工厂方法创建线程池,而不是手动配置线程池。
  • 示例:

    ExecutorService executor = Executors.newFixedThreadPool(10);
    Future<Integer> future = executor.submit(() -> {// 执行任务return 42;
    });try {Integer result = future.get();  // 获取任务执行结果
    } catch (InterruptedException | ExecutionException e) {e.printStackTrace();
    } finally {executor.shutdown();
    }
    
8. 优先使用无锁并发工具
  • 原因:传统的锁机制(如 synchronized)虽然简单易用,但在高并发场景下可能会引发性能问题。无锁并发工具(如 AtomicIntegerAtomicReference 等)能够提供更高效的并发处理。

  • 最佳实践:

    • 使用原子类(如 AtomicIntegerAtomicLong)来代替传统的同步方法,这些类通过底层硬件支持来实现线程安全,避免了锁的开销。
  • 示例:

    private AtomicInteger counter = new AtomicInteger(0);public void increment() {counter.incrementAndGet();  // 原子操作
    }
    

三、小结

《Effective Java》第7部分“并发”提供了有关如何编写高效且线程安全的并发程序的最佳实践。正确地使用并发工具和库,避免共享可变数据、死锁以及无效的同步等问题,能够显著提高程序的性能和可靠性。通过理解并应用这些最佳实践,开发者可以避免常见的并发错误,并编写出更健壮的多线程程序。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词