Android 多线程之几个基本问题

简介:

Android中的进程和线程

  • Android中的一个应用程序一般就对应着一个进程,多进程的情况可以参考Android 多进程通信之几个基本问题
  • Android中更常见的是多线程的情况,一个应用程序中一般都有包括UI线程等多个线程。Android中规定网络访问必须在子线程中进行,而操作更新UI则只能在UI线程。
  • 常见的网络请求库,如OkHttp、Volly等都为我们封装好了线程池,所以我们在进行网络请求时一般不是很能直观地感受到创建线程以及切换线程的过程。
  • 线程是一种很宝贵的资源,要避免频繁创建销毁线程,一般推荐用线程池来管理线程。

线程的状态

线程可能存在6种不同的状态:新创建(New)、可运行(Runnable)、阻塞状态(Blocked)、等待状态(Waiting)、限期等待(Timed Waiting)、终止状态(Terminated)

  • 新创建(New):创建后但还未启动的线程(还没有调用start方法)处于这种状态
  • 可运行(Runnable):一旦调用了start方法,线程就处于这种状态。需要注意的是此时线程可能正在执行,也可能在等待CPU分配执行的时间
  • 阻塞状态(Blocked):表示线程被锁阻塞,等待获取到一个排他锁。在程序等待进入同步区域时,线程将进入这种状态
  • 等待状态(Waiting):处于这种状态的线程不会被分配CPU执行时间,它们要等待被其他线程显示地唤醒。调用以下方法会让线程进入这种状态:

    • 没有设置Timeout参数的Object.wait()方法
    • 没有设置Timeout参数的Thread.join()方法
  • 限期等待(Timed Waiting):与等待状态(Waiting)不同的是,处于这种状态的线程不需要等待其它线程唤醒,在一定时间之后会由系统唤醒。调用以下方法会让线程进入这种状态:

    • Thread.sleep()方法
    • 设置了Timeout参数的Object.wait()方法
    • 设置了Timeout参数的Thread.join()方法
  • 终止状态(Terminated):表示线程已经执行完毕。导致线程终止有2种情况:

    • 线程的run方法执行完毕,正常退出
    • 因为一个没有捕获的异常而终止了run方法

创建线程

创建线程一般有如下几种方式:继承Thread类;实现Runnable接口;实现Callable接口

  • 继承Thread类,重写run方法
public class TestThread extends Thread {
    @Override
    public void run() {
        System.out.println("Hello World");
    }

    public static void main(String[] args) {
        Thread mThread = new TestThread();
        mThread.start();
    }
}
  • 实现Runnable接口,并实现run方法
public class TestRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Hello World");
    }

    public static void main(String[] args) {
        TestRunnable mTestRunnable = new TestRunnable();
        Thread mThread = new Thread(mTestRunnable);
        mThread.start();
    }
}
  • 实现Callable接口,重写call方法

    • Callable可以在任务接受后提供一个返回值而Runnable不行
    • Callable的call方法可以抛出异常,Runnable的run方法不行
    • 运行Callable可以拿到一个Future对象,表示计算的结果,通过Future的get方法可以拿到异步计算的结果,不过当前线程会阻塞。
public class TestCallable {

    public static class MyTestCallable implements Callable<String> {

        @Override
        public String call() throws Exception {
            //call方法可以提供返回值,而Runnable不行
            return "Hello World";
        }
    }

    public static void main(String[] args) {
        MyTestCallable myTestCallable = new MyTestCallable();
        //手动创建线程池
        ExecutorService executorService = new ThreadPoolExecutor(1,1,0L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(10));
        //运行callable可以拿到一个Future对象
        Future future = executorService.submit(myTestCallable);
        try {
            //等待线程结束,future.get()方法会使当前线程阻塞
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
}
  • 以上三种方式就是常见的创建线程的方式。推荐使用实现Runnable接口的方法。

线程中断

  • 当一个线程调用interrupt方法时,线程的中断标识为将被设置成true
  • 通过Thread.currentThread().isInterrupted()方法可以判断线程是否应该被中断
  • 可以通过调用Thread.interrupted()对中断标志位进行复位(设置为false)
  • 如果一个线程处于阻塞状态,线程在检查中断标志位时如果发现中断标志位为true,则会在阻塞方法处抛出InterruptedException异常,并且在抛出异常前会将中断标志位复位,即重新设置为false
  • 不要在代码底层捕获InterruptedException异常后不做处理

同步的几种方法

同步的方式一般有如下3种:volatile关键字、synchronized关键字、重入锁ReentrantLock

volatile关键字
  • volatile关键字实现多线程安全关键在于它的可见性特性,但它需要满足一些条件才能保证线程安全,具体可以查看文章深入理解Java虚拟机(八)之Java内存模型
  • 在用volatile关键字来实现多线程安全时需要注意volatile不保证原子性,也就是不能用于一些自增、自减等操作,也不能用于一些不变式中,自增、自减比较好理解,下面看看不变式的情况
public class VolatileTest {
    private volatile int lower,upper;

    public int getLower() {
        return lower;
    }

    public void setLower(int value) {
        if (value > upper) {
            throw new IllegalArgumentException();
        }
        this.lower = value;
    }

    public int getUpper() {
        return upper;
    }

    public void setUpper(int value) {
        if (value < lower) {
            throw new IllegalArgumentException();
        }
        this.upper = value;
    }
}
  • 上面的例子中,如果初始值是(0,5),线程A调用setLower(4),线程B调用setUpper(3),显然最后结果就会变成(4,3)了
  • volatile使用的场景常见的有作为状态标志以及DCL单例模式
synchronized关键字和重入锁ReentrantLock
  • synchronized关键字比较常见,可以用于同步方法也可以用于同步代码块,一般推荐用同步方法,同步代码块的安全性不高。
  • 重入锁ReentrantLock相比synchronized提供了一些独有的特性:可以绑定多个解锁的条件Condition、可以实现公平锁、可以设置放弃等待获取锁的时间。
public class ReentrantLockTest {
    private Lock mLock = new ReentrantLock();
    //true,表示实现公平锁
    <!--private Lock mLock = new ReentrantLock(true);-->
    private Condition condition;

    private void thread1() throws InterruptedException{
        mLock.lock();
        try {
            condition = mLock.newCondition();
            condition.await();
            System.out.println("thread1:Hello World");
        }finally {
            mLock.unlock();
        }
    }

    private void thread2() throws InterruptedException{
        mLock.lock();
        try {
            System.out.println("thread2:Hello World");
            condition.signalAll();
        }finally {
            mLock.unlock();
        }
    }
}
  • 一个ReentrantLock有多个相关的Condition,调用Condition的await方法会让当前线程进入该条件的等待集并阻塞,直到另一个线程调用了同一个条件的signalAll方法激活因为这个条件而进入阻塞的所有线程
  • 一般线程同步用得比较多的还是synchronized同步方法和一些java.util.concurrent包提供的一些类

如何安全的终止线程

虽然我们一般都是利用线程池来管理线程而不会直接显示地创建线程,但是作为线程相关知识的一部分,我们还是要了解如何安全地终止一个线程。

要安全地终止一个线程,一般有2种方法:中断和标志位

(1)利用中断来终止线程

Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            //do something
        }
    }
});

//当我们调用Thread的interrupt方法时,线程就会退出循环停止了。
thread.interrupt();

(2)通过标志位

private static class MyRunnable implements Runnable {
    //控制线程的标志位,需要用 volatile关键字   
    private volatile boolean on = true;

    @Override
    public void run() {
        while (on) {
           //do something 
        }
    }
    
    public void cancel() {
        on = false;
    }
}

//启动线程
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);

//终止线程
myRunnable.cancel();

              欢迎关注我的微信公众号,期待与你一起学习,一起交流,一起成长!

AntDream

目录
相关文章
|
4月前
|
Java Android开发 UED
🧠Android多线程与异步编程实战!告别卡顿,让应用响应如丝般顺滑!🧵
【7月更文挑战第28天】在Android开发中,确保UI流畅性至关重要。多线程与异步编程技术可将耗时操作移至后台,避免阻塞主线程。我们通常采用`Thread`类、`Handler`与`Looper`、`AsyncTask`及`ExecutorService`等进行多线程编程。
56 2
|
4月前
|
Java Android开发
Android面试题经典之Glide取消加载以及线程池优化
Glide通过生命周期管理在`onStop`时暂停请求,`onDestroy`时取消请求,减少资源浪费。在`EngineJob`和`DecodeJob`中使用`cancel`方法标记任务并中断数据获取。当网络请求被取消时,`HttpUrlFetcher`的`cancel`方法设置标志,之后的数据获取会返回`null`,中断加载流程。Glide还使用定制的线程池,如AnimationExecutor、diskCacheExecutor、sourceExecutor和newUnlimitedSourceExecutor,其中某些禁止网络访问,并根据CPU核心数动态调整线程数。
129 2
|
20天前
|
调度 Android开发 开发者
构建高效Android应用:探究Kotlin多线程优化策略
【10月更文挑战第11天】本文探讨了如何在Kotlin中实现高效的多线程方案,特别是在Android应用开发中。通过介绍Kotlin协程的基础知识、异步数据加载的实际案例,以及合理使用不同调度器的方法,帮助开发者提升应用性能和用户体验。
36 4
|
2月前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android多线程编程的重要性及其实现方法,涵盖了基本概念、常见线程类型(如主线程、工作线程)以及多种多线程实现方式(如`Thread`、`HandlerThread`、`Executors`、Kotlin协程等)。通过合理的多线程管理,可大幅提升应用性能和用户体验。
104 15
一个Android App最少有几个线程?实现多线程的方式有哪些?
|
2月前
|
Java Android开发 UED
🧠Android多线程与异步编程实战!告别卡顿,让应用响应如丝般顺滑!🧵
在Android开发中,为应对复杂应用场景和繁重计算任务,多线程与异步编程成为保证UI流畅性的关键。本文将介绍Android中的多线程基础,包括Thread、Handler、Looper、AsyncTask及ExecutorService等,并通过示例代码展示其实用性。AsyncTask适用于简单后台操作,而ExecutorService则能更好地管理复杂并发任务。合理运用这些技术,可显著提升应用性能和用户体验,避免内存泄漏和线程安全问题,确保UI更新顺畅。
78 5
|
2月前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android应用开发中的多线程编程,涵盖基本概念、常见实现方式及最佳实践。主要内容包括主线程与工作线程的作用、多线程的多种实现方法(如 `Thread`、`HandlerThread`、`Executors` 和 Kotlin 协程),以及如何避免内存泄漏和合理使用线程池。通过有效的多线程管理,可以显著提升应用性能和用户体验。
67 10
|
2月前
|
API Android开发 iOS开发
安卓与iOS开发中的线程管理对比
【9月更文挑战第12天】在移动应用的世界中,安卓和iOS平台各自拥有庞大的用户群体。开发者们在这两个平台上构建应用时,线程管理是他们必须面对的关键挑战之一。本文将深入探讨两大平台在线程管理方面的异同,通过直观的代码示例,揭示它们各自的设计理念和实现方式,帮助读者更好地理解如何在安卓与iOS开发中高效地处理多线程任务。
|
2月前
|
Java Android开发 开发者
安卓应用开发中的线程管理优化技巧
【9月更文挑战第10天】在安卓开发的海洋里,线程管理犹如航行的风帆,掌握好它,能让应用乘风破浪,反之则可能遭遇性能的暗礁。本文将通过浅显易懂的语言和生动的比喻,带你探索如何优雅地处理安卓中的线程问题,从基础的线程创建到高级的线程池运用,让你的应用运行更加流畅。
|
3月前
|
调度 Android开发 开发者
【颠覆传统!】Kotlin协程魔法:解锁Android应用极速体验,带你领略多线程优化的无限魅力!
【8月更文挑战第12天】多线程对现代Android应用至关重要,能显著提升性能与体验。本文探讨Kotlin中的高效多线程实践。首先,理解主线程(UI线程)的角色,避免阻塞它。Kotlin协程作为轻量级线程,简化异步编程。示例展示了如何使用`kotlinx.coroutines`库创建协程,执行后台任务而不影响UI。此外,通过协程与Retrofit结合,实现了网络数据的异步加载,并安全地更新UI。协程不仅提高代码可读性,还能确保程序高效运行,不阻塞主线程,是构建高性能Android应用的关键。
57 4
|
3月前
|
安全 网络安全 数据安全/隐私保护
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享安卓与iOS开发中的线程管理比较
【8月更文挑战第30天】本文将探讨网络安全与信息安全的重要性,并分享关于网络安全漏洞、加密技术和安全意识的知识。我们将了解常见的网络攻击类型和防御策略,以及如何通过加密技术和提高安全意识来保护个人和组织的信息安全。