首页 > 编程语言 >深入Android多线程编程与性能优化

深入Android多线程编程与性能优化

时间:2023-11-22 18:33:02浏览次数:31  
标签:执行 Thread 编程 并发 线程 new Android 多线程

引言

在上一篇的入门篇中,我们对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();
}
  • ReentrantLockLock接口的一种实现,提供了可重入的锁。
  • condition.await()用于让线程等待,condition.signal()用于唤醒等待的线程。

并发数据结构

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

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

ConcurrentHashMap

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

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

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

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

CopyOnWriteArrayList

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

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。当线程中发生未捕获的异常时,UncaughtExceptionHandleruncaughtException 方法将被调用,我们在这里简单地输出了异常信息。

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

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

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

Android中的异步任务与HandlerThread

AsyncTask的替代方案

由于AsyncTask已被废弃,推荐使用ExecutorHandler的结合,或者使用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应用。

标签:执行,Thread,编程,并发,线程,new,Android,多线程
From: https://blog.51cto.com/u_16175630/8519177

相关文章

  • Android 的PAI 简介
    PAI简介在Google的Android操作系统中,PAI(PreinstalledAppsInfrastructure)预安装程序基础设施是指在设备出厂时预先安装在系统中的一组应用程序。这些应用程序通常是由设备制造商或运营商选择的,并且它们在设备启动时就已经存在,用户可以在使用设备时直接访问这些应用。预安装介绍......
  • 作为一个Android初级开发工程师,该如何进阶?
    前言现今Android行业初级人才已逐渐饱和化,但中高级人才却依旧很稀缺,身边HR朋友经常遇到的情况是:100份简历里只有2、3个比较合适的候选人,大部分的人都是不合格的!有97%的Android技术人都会面临这些困境(或许也是你的困惑):缺乏技术广度和深度:如果你长期在小型软件公司或外包工......
  • 关于阻塞多线程
    关于阻塞多线程同步方式理解:一个循环循环100次。多线程方式理解:开10个循环同时执行循环,每个循环循环10次。......
  • python多线程中一种错误的写法
    直接先上错误代码:importmultiprocessingdeffirst_way():init=3defprocess_function(item):result=item*initreturnresultdata=[1,2,3,4,5,6,7,8,9,10]pool=multiprocessing.Pool(processes=4)#创建一个......
  • Spring5学习随笔-基础注解编程
    学习视频:【孙哥说Spring5:从设计模式到基本应用到应用级底层分析,一次深入浅出的Spring全探索。学不会Spring?只因你未遇见孙哥】注解编程-第一章、注解基础概念1.什么是注解编程指的是在类或方法上加入特定的注解(@XXX),完成特定功能的开发.2.为什么要讲解注解编程注解开发......
  • (二十三)C#编程基础复习——Struct结构体
    在C#中,结构体也称为结构类型("structuretype”或“structtype”),它是一种可封装数据和相关功能的值类型,在语法上结构体与类(class)非常相似,它们都可以用来封装数据,并且都可以包含成员属性和成员方法。一、定义结构体要定义一个结构体需要使用struct关键字,每个结构体都可以被看作......
  • Linux多线程
    文章参考:爱编程的大丙(subingwen.cn)一.线程概述线程是一种轻量级的,在Linux环境下,由于Linux内核起初并未设计线程,只有进程,因此将线程本质上仍是进程。而在实际处理中,进程是操作系统最小的分配资源单位,而线程是操作系统最小的调度执行单位。区别如下:空间上:每一个进程都有......
  • 实验2 C语言分支与循环基础应用编程
    实验任务11#include<stdio.h>2#include<stdlib.h>3#include<time.h>45#defineN56#defineN13747#defineN24658intmain(){9intnumber;10inti;11srand(time(0));12for(i=0;i<N;++i){13nu......
  • 多线程的应用
    应用之异步调用以调用方角度来讲,如果需要等待结果返回,才能继续运行就是同步不需要等待结果返回,就能继续运行就是异步1)设计多线程可以让方法执行变为异步的(即不要巴巴干等着)比如说读取磁盘文件时,假设读取操作花费了5秒钟,如果没有线程调度机制,这5秒cpu什么都做不了,其它代......
  • shell 编程条件语句
    shelltest  测试0为真test-a/etc/fstabecho$?test-e/etc/fstabecho$? -a,-e#测试文件是否存在-a有bug#取反会有变化test+选项对象参数test-f#只看文件-r#是否有读的权限-w#是否有写的权限-x#是否有执行的权限-d#目录-f#文件[-e/etc/fs......