多线程与并发编程是 Android 开发中的重要内容,掌握它们能够帮助你更高效地处理后台任务,提升应用的响应速度和流畅度。

1. 基本概念

线程与进程:理解线程和进程的区别,线程是程序执行的基本单位,一个进程可以包含多个线程。

并发与并行:并发指的是多个任务在同一时间段内交替进行,可能是单核 CPU 上的时间片轮转;并行则是多个任务在多核 CPU 上同时执行。

死锁、竞态条件与活锁:理解并发编程中的常见问题,学会如何避免它们。

  • 死锁:多个线程互相等待对方释放资源,导致程序无法继续执行。
  • 竞态条件:多个线程并发访问共享资源时,结果依赖于执行顺序,导致程序行为不确定。
  • 活锁:线程不断地互相调整状态,但永远无法完成任务。

2. Java 中的线程基础

Thread 类与 Runnable 接口

  • Thread 类是 Java 中创建线程的传统方式,你可以继承 Thread 类并重写 run() 方法。
  • Runnable 接口是一种更灵活的方式,线程通过实现 Runnable 接口的 run() 方法来执行任务。

线程启动方式:

// 使用 Thread 类(第一种方式更加简洁,且两者的效果相同)
Thread thread = new Thread(new MyRunnable());
thread.start();

// 使用 Runnable 接口
Runnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();

3. 线程池(Executor Framework)

Executor:Java 提供了 Executor 接口和 ExecutorService 接口来简化线程管理,避免直接管理线程池。常用的实现类包括 ThreadPoolExecutorScheduledThreadPoolExecutor

线程池的创建

  • Executors.newFixedThreadPool(int nThreads):创建一个固定大小的线程池。
  • Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池。
  • Executors.newSingleThreadExecutor():创建一个单线程池。

线程池的使用

ExecutorService executorService = Executors.newFixedThreadPool(4);
executorService.submit(new MyRunnable()); // 提交任务到线程池
executorService.shutdown(); // 关闭线程池

4. 同步与互斥

synchronized 关键字:通过 synchronized 关键字来保证同一时刻只有一个线程可以执行同步代码块,避免竞态条件。

 

  • 同步方法public synchronized void method() { }
  • 同步代码块synchronized (object) { }

 

死锁:避免死锁的一种方法是按固定顺序请求资源,或使用 tryLock() 防止长时间等待锁。

ReentrantLockReentrantLockjava.util.concurrent 包中的一种锁,它比 synchronized 更加灵活,支持尝试锁定和定时锁定。

 

  • lock()unlock():手动获取和释放锁。
  • tryLock():尝试获取锁,如果无法获得锁则立即返回。

5. 并发容器

Concurrent Collectionsjava.util.concurrent 包提供了一些线程安全的集合类,解决了多线程环境下常规集合类的并发问题。

 

  • ConcurrentHashMap:线程安全的 Map 实现,支持并发读取和写入。
  • CopyOnWriteArrayList:线程安全的 List 实现,适合在读取多、写入少的场景使用。
  • BlockingQueue:线程安全的队列,提供了阻塞操作。

6. 高级同步机制

 

  • CountDownLatch:一个同步辅助工具,用于阻塞一个或多个线程,直到其他线程执行完预定的操作。
  • CyclicBarrier:一个同步工具,它允许一组线程互相等待,直到所有线程都达到某个共同屏障点。
  • Semaphore:控制同时访问某个资源的线程数。
  • Exchanger:两个线程通过此工具交换数据。

7. 并发工具类

ExecutorService:管理线程池并执行任务。

ScheduledExecutorService:执行定时任务,例如周期性任务。

ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(“Scheduled task”);
}
}, 0, 1, TimeUnit.SECONDS); // 延迟 0 秒后每秒执行一次

ForkJoinPool:针对大规模数据处理的线程池,使用工作窃取算法,将任务分解成多个小任务并行处理。

8. Java 8 引入的并发功能

CompletableFuture:是 Future 的增强版,允许你更灵活地组合多个异步任务。

CompletableFuture.supplyAsync(() -> {
return “Task Result”;
}).thenAccept(result -> {
System.out.println(result);
});

Lambda 表达式与并发:Java 8 的 Lambda 表达式可以让线程池的任务定义变得更加简洁和易读。

9. Android 中的多线程编程

Android 中的 UI 操作必须在主线程(即 UI 线程)中执行,而耗时操作应在子线程中执行。常见的 Android 多线程编程工具和技巧包括:

AsyncTask:虽然已经被弃用,但仍然在老项目中广泛使用,适用于简单的后台任务执行。

new AsyncTask<Void, Void, String>() {
@Override
protected String doInBackground(Void… params) {
// 执行后台任务
return “Result”;
}

@Override
protected void onPostExecute(String result) {
// 在主线程更新 UI
textView.setText(result);
}
}.execute();

Handler 和 Looper:通过 Handler 将任务从后台线程发送到主线程,更新 UI。

Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
@Override
public void run() {
// 在主线程中执行任务,例如更新 UI
}
});

10. Kotlin 协程

Kotlin 协程(Coroutines)提供了一个更简洁、易用的方式来处理异步任务。它通过挂起函数(suspend)和轻量级线程来简化并发编程。

  • launchasync:这两个函数分别用于启动协程并执行任务。
  • suspend 函数:用于挂起当前协程并在后台执行任务,不阻塞线程。
  • withContext:切换上下文,确保耗时任务在后台线程执行,更新 UI 时切回主线程。

GlobalScope.launch {
val result = async { longRunningTask() }
withContext(Dispatchers.Main) {
textView.text = result.await()
}
}

11. 调度与线程管理

  • 线程池管理:通过合理配置线程池的大小,避免过多的线程创建和销毁带来的性能开销。
  • 线程优先级:在多线程环境中调整线程的优先级,确保重要任务优先执行。

12. 性能优化

  • 避免过度创建线程:过多的线程会引起上下文切换,降低性能。
  • 减少锁的使用:过多的锁会导致线程阻塞,影响性能。使用读写锁、ReentrantLock 等优化锁的使用。
  • 内存泄漏和 GC:确保线程中的对象不会被无意间引用,导致内存泄漏。

 

 

 

 

作者 admin

百度广告效果展示