多线程编程与性能优化

2023-12-18

引言

在上一篇的入门篇中,我们对Android线程的基础概念和多线程编程模型有了初步了解。本篇将深入探讨多线程编程技术和性能优化策略,以提升应用的效率和响应性。

高级多线程编程技术

使用线程池管理线程

线程池是一组预先创建的线程,用于执行任务。通过使用线程池,可以避免不断创建和销毁线程的开销,提高线程的重用率,同时有效控制并发线程数量。

// 创建固定大小的线程池
ExecutorService executor = Executors.newFixedThreadPool(5);

// 提交任务给线程池执行
executor.submit(() -> {
    // 执行任务的代码
});
  • 通过 Executors.newFixedThreadPool 创建一个包含5个线程的固定大小线程池。

  • executor.submit 方法用于将任务提交给线程池执行。

使用Callable和Future获取任务结果

Callable 接口允许线程返回结果,而 Future 接口允许主线程获取线程的执行结果。

Callable<String> callableTask = () -> {
    // 执行任务的代码
    return "Task completed";
};

Future<String> future = executor.submit(callableTask);

// 获取任务结果
String result = future.get();
  • Callable 接口相比 Runnable 接口,允许在执行完任务后返回一个结果。

  • executor.submit 返回一个 Future 对象,可以通过 get 方法获取任务执行的结果。

使用Lock和Condition进行更精细的同步控制

synchronized 关键字相比, Lock 接口提供了更灵活的锁定机制, Condition 接口允许线程等待特定条件满足后再继续执行。

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();

// 在线程中使用锁
lock.lock();
try {
    // 一些需要同步的代码块
    condition.await(); // 等待条件满足
} finally {
    lock.unlock();
}

// 在另一个线程中发出信号
lock.lock();
try {
    condition.signal(); // 发送信号
} finally {
    lock.unlock();
}
  • ReentrantLock Lock 接口的一种实现,提供了可重入的锁。

  • condition.await() 用于让线程等待, condition.signal() 用于唤醒等待的线程。

并发数据结构

并发数据结构是在多线程编程中至关重要的一部分。并发数据结构提供了在多线程环境下安全访问和修改数据的机制,以确保线程安全性和避免竞态条件。

以下是一些常见的并发数据结构及其应用

ConcurrentHashMap

ConcurrentHashMap HashMap 的线程安全版本,它允许在不同的部分上并发地读写数据,提高了并发性能。在多线程环境中,使用 ConcurrentHashMap 可以避免对整个数据结构的锁定,从而提高并发性。

ConcurrentHashMap<String, Integer> concurrentMap = new ConcurrentHashMap<>();

concurrentMap.put("key1", 1);
concurrentMap.put("key2", 2);

int value = concurrentMap.get("key1");

CopyOnWriteArrayList

CopyOnWriteArrayList ArrayList 的线程安全版本,它通过在修改操作时创建底层数组的副本来保证线程安全。它适用于读多写少的场景。

CopyOnWriteArrayList<String> copyOnWriteArrayList = new CopyOnWriteArrayList<>();

copyOnWriteArrayList.add("Item1");
copyOnWriteArrayList.add("Item2");

String item = copyOnWriteArrayList.get(0);

BlockingQueue

BlockingQueue 是一个阻塞队列,它提供了在队列为空或已满时阻塞线程的操作。这在生产者-消费者模型中特别有用。

BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>(10);

// 生产者线程
blockingQueue.put("Item");

// 消费者线程
String item = blockingQueue.take();

AtomicInteger

AtomicInteger 是一个原子整数,它提供了一组原子操作,可确保在多线程环境中进行递增、递减等操作时的线程安全性。

AtomicInteger atomicInteger = new AtomicInteger(0);

int result = atomicInteger.incrementAndGet();  // 原子递增操作

CountDownLatch

CountDownLatch 是一个同步工具类,它允许一个或多个线程等待其他线程完成操作。

CountDownLatch latch = new CountDownLatch(2);

// 线程1
new Thread(() -> {
    // 执行某些操作
    latch.countDown();
}).start();

// 线程2
new Thread(() -> {
    // 执行某些操作
    latch.countDown();
}).start();

// 主线程等待两个线程完成
latch.await();

CyclicBarrier

CyclicBarrier 是另一个同步工具类,它允许一组线程相互等待,直到所有线程都到达某个屏障点。

CyclicBarrier cyclicBarrier = new CyclicBarrier(3);

// 线程1
new Thread(() -> {
    // 执行某些操作
    cyclicBarrier.await();
}).start();

// 线程2
new Thread(() -> {
    // 执行某些操作
    cyclicBarrier.await();
}).start();

// 线程3
new Thread(() -> {
    // 执行某些操作
    cyclicBarrier.await();
}).start();

// 主线程等待所有线程到达屏障点
cyclicBarrier.await();

这些并发数据结构为多线程编程提供了有力的支持,但在使用它们时需要谨慎,以确保正确地处理并发访问和修改。在合适的场景下,合理使用这些数据结构可以提高程序的性能和并发度。

异常处理

UncaughtExceptionHandler 接口可以用于捕获线程中未处理的异常。通过设置线程的 UncaughtExceptionHandler ,可以在异常发生时执行自定义的处理逻辑。

public class UncaughtExceptionHandlerExample {

    public static void main(String[] args) {
        // 创建一个线程
        Thread thread = new Thread(() -> {
            // 模拟发生异常
            throw new RuntimeException("Simulated exception");
        });

        // 设置线程的UncaughtExceptionHandler
        thread.setUncaughtExceptionHandler((t, e) ->
                System.out.println("Uncaught exception in thread " + t.getName() + ": " + e.getMessage()));

        // 启动线程
        thread.start();
    }
}

在这个例子中,我们创建了一个线程,并设置了它的 UncaughtExceptionHandler 。当线程中发生未捕获的异常时, UncaughtExceptionHandler uncaughtException 方法将被调用,我们在这里简单地输出了异常信息。

请注意,这种机制对于捕获主线程的异常可能不太适用。对于主线程的异常处理,更好的方式是使用 Thread.setDefaultUncaughtExceptionHandler 来设置默认的异常处理器。

Thread.setDefaultUncaughtExceptionHandler((t, e) ->
        System.out.println("Uncaught exception in thread " + t.getName() + ": " + e.getMessage()));

这将影响所有线程,除非它们显式地设置了自己的 UncaughtExceptionHandler

Android中的异步任务与HandlerThread

AsyncTask的替代方案

由于 AsyncTask 已被废弃,推荐使用 Executor Handler 的结合,或者使用Kotlin协程(在入门篇中已有介绍)来执行异步任务。

// 使用Executor执行异步任务
Executor executor = Executors.newSingleThreadExecutor();

executor.execute(() -> {
    // 执行异步任务的代码
});

HandlerThread的使用

HandlerThread 是Android中的一个辅助类,它封装了与Looper相关的操作,使得在后台线程中执行任务更为方便。

HandlerThread handlerThread = new HandlerThread("MyHandlerThread");
handlerThread.start();

Handler handler = new Handler(handlerThread.getLooper());

// 在后台线程执行任务
handler.post(() -> {
    // 执行任务的代码
});

性能优化策略

使用Systrace进行性能分析

Systrace是Android系统提供的一种性能分析工具,可以用来检查应用中的性能瓶颈,找到耗时操作,优化线程执行时间。

  • 通过Android Studio中的Profiler工具,选择Systrace进行性能分析。

避免UI线程阻塞

将耗时任务转移到后台线程执行,确保UI线程不会因为耗时操作而阻塞,提高应用的响应性。

// 使用Handler将任务提交到后台线程执行
Handler handler = new Handler(Looper.getMainLooper());

handler.post(() -> {
    // 执行耗时任务
});

使用轻量级的同步机制

避免过多使用重量级的同步机制,如 synchronized 关键字,可以选择使用 java.util.concurrent 包中的更轻量级的机制,例如 ReentrantLock

优化内存管理

及时释放不再需要的对象,避免内存泄漏。可以使用工具如 LeakCanary 来帮助检测内存泄漏问题。

// 使用WeakReference来持有对象的弱引用,有助于及时释放不再需要的对象
WeakReference<MyObject> weakReference = new WeakReference<>(myObject);
myObject = null; // 释放强引用

总结

我们深入了解了Android中更高级的多线程编程技术,包括线程池的使用、锁与条件的灵活应用、并发数据结构以及一些性能优化的策略。希望读者能够在实际项目中灵活运用这些知识,构建高效稳定的Android应用。

转自: Android 常见热修复方案及原理

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

多线程编程与性能优化 的相关文章

  • Qt启动页多线程

    Qt启动页多线程 项目需要在Qt进入启动页之前加载一个大模型文件 同时要有一个页面用来提示用户加载状态 这里就需要用到多线程了 如果在单线程操作 要么需要等到文件加载完毕后才能显示等待页 要么干脆跳过了模型文件的加载 都是不符合需求的 我们
  • Future 和 Callable

    一 Runnable 缺陷 不能返回一个返回值 不能抛出 checked Execption 二 Callable接口 类似于Runnable 被其他线程执行的任务 实现call方法 有返回值 三 Future的作用 Callable和Fu
  • 【Unity Optimize】使用对象池(Object Pooling)优化项目

    目录 1 对象池 Object Pooling 介绍 2 实现对象池脚本 3 使用对象池生成Cube 4 效果展示 5 Unity资源商店的对象池插件 1 对象池 Object Pooling 介绍 Unity中的对象池 Object Po
  • 多线程太可怕了

    今天发现了一个多线程引起的bug 然后进一步体会到 这东西太容易出问题了 首先要说明的是 出问题的代码可不是一般人写的 是由一个叫EPAM systems的世界知名外包公司的人写的 这些java程序员个个经验丰富 心高气傲 貌似base在乌
  • 什么是javascript内存泄漏?以及解决方法

    什么是javascript内存泄漏 以及解决方法 一 什么是javascript内存泄漏 二 常见的内存泄漏 1 意外的全局变量 通常是变量未被定义或者胡乱引用了全局变量 2 计时器 3 闭包 4 事件监听未被移除 5 console lo
  • java使用线程池批量插入mysql数据

    首先我们使用最原始的for循环插入数据 for int i 0 i lt 100000000 i service add new LongTest setStatus 1 setName NumberUtil getPwdRandom 5
  • 关于Semaphore信号量的源码解读

    Semaphore的简单使用 利用Semaphore可以实现对线程数量的控制 比如如下的代码 class SemaphoreTest public static void main String args Semaphore semapho
  • 史上最全Android性能优化方案解析

    Android中的性能优分为以下几个方面 布局优化 网络优化 安装包优化 内存优化 卡顿优化 启动优化 一 布局优化 布局优化的本质就是减少View的层级 常见的布局优化方案如下 在LinearLayout和RelativeLayout都可
  • Cuda Streams的概述(四)-- 同步

    同步 同步的APIs 同步所有的事情 阻塞host端 直到所有的CUDA调用完成 cudaDeviceSynchronize 同步主机端特定的流 阻塞host端 直到流里的CUDA调用完成 cudaStreamSynchronize str
  • 多线程快速处理List集合(结合线程池的使用)

    有一个大List集合 遍历进行一些耗时操作 不能达到性能要求 查询日志 单个任务虽然有不少数据库和第三方API请求 比较耗时 但返回效率尚可 所以优先采用多线程方式进行处理并行请求数据库和第三方API 因为处理完还要对list所属的数据进行
  • Java 多线程模式 —— Guarded Suspension 模式

    Part1Guarded Suspension 模式的介绍 我们只从字面上看 Guarded Suspension 是受保护暂停的意思 1Guarded Suspension 模式 在实际的并发编程中 Guarded Suspension
  • Jmeter动态吞吐量实现

    在容量测试时 控量 是非常重要的 JMeter 是根据线程数大小来控制压力强弱的 但我们制定的压测目标中的指标往往是吞吐量 QPS TPS 这就给测试人员带来了不便之处 必须一边调整线程数 一边观察 QPS TPS 达到什么量级了 为了解决
  • Android 性能优化系列:崩溃原因及捕获

    文章目录 崩溃的基本原因 抛出异常导致崩溃分析 AMS 如何承接应用的异常信息上报 对于 native crash 系统如何做处理 系统如何处理 ANR 异常数据 addErrorToDropBox DropBoxManager 在 Cra
  • KMP比较简单的讲法。

    转载链接 http blog csdn net yearn520 article details 6729426 我们在一个母字符串中查找一个子字符串有很多方法 KMP是一种最常见的改进算法 它可以在匹配过程中失配的情况下 有效地多往后面跳
  • 多线程事务怎么回滚?说用 @Transactional 可以回去等通知了!

    背景介绍 1 最近有一个大数据量插入的操作入库的业务场景 需要先做一些其他修改操作 然后在执行插入操作 由于插入数据可能会很多 用到多线程去拆分数据并行处理来提高响应时间 如果有一个线程执行失败 则全部回滚 2 在spring中可以使用 T
  • ScheduledThreadPoolExecutor周期定时任务异常处理踩坑的问题!!

    问题原因 在公司写项目的时候 有一个周期定时任务的需求 就想着阿里巴巴开发手册里不是说不能用Executors去创建线程池 因为存在如下问题 FixedThreadPool和SingleThreadPool 允许的请求队列长度为 Integ
  • Redis10大性能优化策略

    1 Redis为什么变慢了 1 Redis真的变慢了吗 对 Redis 进行基准性能测试 例如 我的机器配置比较低 当延迟为 2ms 时 我就认为 Redis 变慢了 但是如果你的硬件配置比较高 那么在你的运行环境下 可能延迟是 0 5ms
  • TaskDecatator用法

    在Spring框架中 TaskDecorator 是一个接口 它可以用来自定义由 ThreadPoolTaskExecutor 或其他任务执行器管理的任务的装饰行为 这通常用于在执行任务之前和之后添加某些上下文相关的行为 比如设置线程上下文
  • Golang拼接字符串性能对比

    g o l a n g golang g o l an g
  • JUC的常见类

    目录 Callable ReentrantLock Semaphore CountDownLatch JUC 即 java util concurrent 其中存放了一些进行多线程编程时有用的类 Callable Callable是一个接口

随机推荐