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

目录
相关文章
|
12天前
|
Java 数据库 Android开发
【专栏】构建高效 Android 应用:探究 Kotlin 多线程优化策略
【4月更文挑战第27天】本文探讨了Kotlin在Android开发中的多线程优化,包括线程池、协程的使用,任务分解、避免阻塞操作以及资源管理。通过案例分析展示了网络请求、图像处理和数据库操作的优化实践。同时,文章指出并发编程的挑战,如性能评估、调试及兼容性问题,并强调了多线程优化对提升应用性能的重要性。开发者应持续学习和探索新的优化策略,以适应移动应用市场的竞争需求。
|
2月前
|
Java 调度 Android开发
构建高效Android应用:探究Kotlin多线程编程
【2月更文挑战第17天】 在现代移动开发领域,性能优化一直是开发者关注的焦点。特别是在Android平台上,合理利用多线程技术可以显著提升应用程序的响应性和用户体验。本文将深入探讨使用Kotlin进行Android多线程编程的策略与实践,旨在为开发者提供系统化的解决方案和性能提升技巧。我们将从基础概念入手,逐步介绍高级特性,并通过实际案例分析如何有效利用Kotlin协程、线程池以及异步任务处理机制来构建一个更加高效的Android应用。
|
4月前
|
Java 调度 数据库
Android 性能优化: 如何进行多线程编程以提高应用性能?
Android 性能优化: 如何进行多线程编程以提高应用性能?
53 0
|
8月前
|
存储 SQL 安全
Android面试中问的线程相关问题
Android面试中问的线程相关问题
41 0
|
26天前
|
Java API 调度
安卓多线程和并发处理:提高应用效率
【4月更文挑战第13天】本文探讨了安卓应用中多线程和并发处理的优化方法,包括使用Thread、AsyncTask、Loader、IntentService、JobScheduler、WorkManager以及线程池。此外,还介绍了RxJava和Kotlin协程作为异步编程工具。理解并恰当运用这些技术能提升应用效率,避免UI卡顿,确保良好用户体验。随着安卓技术发展,更高级的异步处理工具将助力开发者构建高性能应用。
|
1月前
|
安全 Linux API
Android进程与线程
Android进程与线程
22 0
|
2月前
|
API 数据库 Android开发
构建高效Android应用:探究Kotlin多线程优化策略
随着移动设备性能的日益强大,用户对应用程序的响应速度和流畅性要求越来越高。在Android开发中,合理利用多线程技术是提升应用性能的关键手段之一。Kotlin作为一种现代的编程语言,其协程特性为开发者提供了更为简洁高效的多线程处理方式。本文将深入探讨使用Kotlin进行Android多线程编程的最佳实践,包括协程的基本概念、优势以及在实际项目中的应用场景和性能优化技巧,旨在帮助开发者构建更加高效稳定的Android应用。
|
2月前
|
Java Android开发 开发者
构建高效Android应用:探究Kotlin多线程优化策略
【2月更文挑战第17天】 随着移动设备性能的不断提升,用户对应用的响应速度和稳定性要求越来越高。在Android开发中,Kotlin语言以其简洁、安全的特点受到开发者青睐。然而,面对复杂的多线程任务,如何有效利用Kotlin进行优化,以提升应用性能,是本文探讨的重点。通过分析Kotlin并发工具的使用场景与限制,结合实例演示其在Android开发中的实践,旨在为开发者提供实用的多线程处理指南。
|
9月前
|
Android开发
Android 中ProgressDialog进度条对话框的使用(使用子线程模拟更新进度)
Android 中ProgressDialog进度条对话框的使用(使用子线程模拟更新进度)
104 0
|
9月前
|
安全 Java Android开发
Android 中AsyncTask后台线程,异步任务的理解
Android 中AsyncTask后台线程,异步任务的理解
102 0