❤️Android 进程与线程 ❤️不好不要钱(下)

简介: 小结实现Callable和实现Runnable类似,但是功能更强大,具体表现在:• 可以在任务结束后提供一个返回值,Runnable不行。• call方法可以抛出异常,Runnable的run方法不行。• 可以通过运行Callable得到的Fulture对象监听目标线程调用call方法的结果,得到返回值,(fulture.get(),调用后会阻塞,直到获取到返回值)。

3、Android中的线程


线程分为两种:


  • UI/Main Thread (主线程)


  • Worker Thread(工作线程)


       一个线程总是由另一个线程启动,所以总有一个特殊的线程,叫做主线程。它是应用启动并执行的第一个线程。每次启动一个新工作线程,都会从主线程分出一条独立的线。

微信图片_20220523225750.png



3.1 UI/Main Thread (主线程)


      启动应用程序时,系统会为应用程序创建一个执行线程,称为 "main"。该线程非常重要,因为它负责将事件发送到适当的用户界面小部件,包括绘图事件。与Android UI toolkit (来自Android.widget和Android.view包的组件)交互的线程。


       因此,主线程有时也称为UI线程。但是,在特殊情况下,应用程序的主线程可能不是它的UI线程。


注意:构建工具将@MainThread和 @UiThread注释视为可互换的,因此你可以@UiThread 从@MainThread方法中调用方法,反之亦然。但是,在系统应用程序在不同线程上具有多个视图的情况下,UI 线程可能与主线程不同。因此,你应该 @UiThread 使用 @MainThread.


      在同一进程中运行的所有组件都在UI线程中实例化。


       此外,Android UI toolkit不是线程安全的。因此,你不能从工作线程操作UI—你必须从UI线程对用户界面执行所有操作。因此,Android的单线程模型只有两条规则:


  • 不要阻塞UI线程;


  • 不要在非UI线程访问 UI 。


3.1.1 阻塞UI线程


       如果所有事情都发生在UI线程中,那么执行长时间操作(如网络访问或数据库查询)将阻塞整个UI。


发生ANR的原因:


  • Activity超过5秒无响应;


  • BroadcastReceiver超过10秒无响应。


3.1.2 Worker Thread操作UI


@Override
protected void onCreate(@Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_thread);
    //Worker Thread(工作线程)
    new Thread(new Runnable() {
        @Override
        public void run() {
            //操作UI线程
            Toast.makeText(ThreadActivity.this,"我是Worker Thread",Toast.LENGTH_SHORT).show();
        }
    }).start();
}


运行后直接报错:


2021-10-12 14:47:47.495 4122-4247/com.scc.demo E/AndroidRuntime: FATAL EXCEPTION: Thread-7
    Process: com.scc.demo, PID: 4122
    java.lang.RuntimeException: Can't toast on a thread that has not called Looper.prepare()
        at android.widget.Toast$TN.<init>(Toast.java:895)
        at android.widget.Toast.<init>(Toast.java:205)
        at android.widget.Toast.makeText(Toast.java:597)
        at android.widget.Toast.makeText(Toast.java:566)
        at com.scc.demo.actvitiy.ThreadActivity$1.run(ThreadActivity.java:18)
        at java.lang.Thread.run(Thread.java:919)


3.2 Worker Thread(工作线程)


       因不能阻塞主线程,但是有些耗时操作(如加载图片、网络请求等)非即时相应的则可以通过工作线程来执行


注意,你不能从UI线程或"主"线程以外的任何线程更新UI。


为了解决这个问题,Android提供了几种从其他线程访问UI线程的方法:


  • Activity.runOnUiThread(Runnable)


  • View.post(Runnable)


  • View.postDelayed(Runnable, long)


3.2.1 样例:子线程访问UI线程


public class ThreadActivity extends ActivityBase{
    TextView tvName;
    @Override
    protected void onCreate(@Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_thread);
        tvName = findViewById(R.id.tv_name);
        tvName.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                csThread();
                startThread();
            }
        });
    }
    private void csThread(){
        //Worker Thread(工作线程)
        new Thread(new Runnable() {
            @Override
            public void run() {
                //这样写直接报错
                tvName.setText("我是Worker Thread---行路难!行路难!");
//                ------强大的分割线------
//                下面几种方式都没问题
                //第一种:Activity.runOnUiThread(Runnable)
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        tvName.setText("我是Worker Thread---行路难!行路难!");
                        Toast.makeText(ThreadActivity.this,"我是Worker Thread",Toast.LENGTH_SHORT).show();
                    }
                });
                //第二种:View.post(Runnable)
                tvName.post(new Runnable() {
                    @Override
                    public void run() {
                        tvName.setText("我是Worker Thread---行路难!行路难!");
                        Toast.makeText(ThreadActivity.this,"我是Worker Thread",Toast.LENGTH_SHORT).show();
                    }
                });
                //第三种:View.postDelayed(Runnable, long)
                tvName.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        tvName.setText("我是Worker Thread---行路难!行路难!");
                        Toast.makeText(ThreadActivity.this,"我是Worker Thread",Toast.LENGTH_SHORT).show();
                    }
                },1000);
                //第四种:Handler
                new Handler(Looper.getMainLooper()).post(new Runnable() {
                    @Override
                    public void run() {
                        tvName.setText("我是Worker Thread---行路难!行路难!");
                        Toast.makeText(ThreadActivity.this,"我是Worker Thread",Toast.LENGTH_SHORT).show();
                    }
                });
            }
        }).start();
    }
}


子线程直接操作主线程报错信息:


       理论上应该拿 3.1.2 Worker Thread 操作UI 时的报错信息。既然都能通过这种方式解决,就多举一个。


2021-10-12 16:02:51.754 8635-8676/com.scc.demo E/AndroidRuntime: FATAL EXCEPTION: Thread-2
    Process: com.scc.demo, PID: 8635
    android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:8798)
        at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1606)
        at android.view.View.requestLayout(View.java:25390)
        ...
        at android.widget.TextView.checkForRelayout(TextView.java:9719)
        at android.widget.TextView.setText(TextView.java:6311)
        ...
        at com.scc.demo.actvitiy.ThreadActivity$2.run(ThreadActivity.java:31)
        at java.lang.Thread.run(Thread.java:923)


3.2.2 Android提供了几种从其他线程访问UI线程的方法源码


    Activity.runOnUiThread(Runnable)
    public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }
    View.post(Runnable)
    public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }
        getRunQueue().post(action);
        return true;
    }
    View.postDelayed(Runnable, long)
    public boolean postDelayed(Runnable action, long delayMillis) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.postDelayed(action, delayMillis);
        }
        getRunQueue().postDelayed(action, delayMillis);
        return true;
    } 


你会发现他们都是使用 Handler 来完成的。所以在 3.2.1 的样例中咱可以使用 new Handler() 来完成更新 UI。


3.3 线程的状态


  • new:新建状态,new出来,还没有调用start。


  • Runnable:可运行状态,调用start进入可运行状态,可能运行也可能没有运行,取决于操作系统的调度。


  • Blocked:阻塞状态,被锁阻塞,暂时不活动,阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态。


  • Waiting:等待状态,不活动,不运行任何代码,等待线程调度器调度,wait sleep。


  • Timed Waiting:超时等待,在指定时间自行返回。


  • Terminated:终止状态,包括正常终止和异常终止。


3.4 开启线程的三种方式


  • 1:继承Thread重写run方法。


  • 2:实现Runnable重写run方法。


  • 3:实现Callable重写call方法。


    private void startThread(){
        //第一种:继承Thread重写run方法
        new MyThread().start();
        //第二种:实现Runnable重写run方法
        new Thread(new MyRunanble()).start();
        //第三种:实现Callable重写call方法
        FutureTask<Integer> ft = new FutureTask<Integer>(new MyCallable());
        new Thread(ft).start();
    }
    class MyThread extends Thread{
        @Override
        public void run() {
            MLog.e(this.getClass().getName());
        }
    }
    class MyRunanble implements Runnable{
        @Override
        public void run() {
            MLog.e(this.getClass().getName());
        }
    }
    class MyCallable implements Callable {
        @Override
        public Object call() throws Exception {
            MLog.e(this.getClass().getName());
            return null;
        }
    }


小结


实现Callable和实现Runnable类似,但是功能更强大,具体表现在:


  • 可以在任务结束后提供一个返回值,Runnable不行。


  • call方法可以抛出异常,Runnable的run方法不行。


  • 可以通过运行Callable得到的Fulture对象监听目标线程调用call方法的结果,得到返回值,(fulture.get(),调用后会阻塞,直到获取到返回值)。


3.5 run()和start()方法区别


  • run():方法只是线程的主体方法,和普通方法一样,不会创建新的线程。


  • start():只有调用start()方法,才会启动一个新的线程,新线程才会调用run()方法,线程才会开始执行。


3.6 wait、notify、notifyAll


  • wait():释放obj的锁,导致当前的线程等待,直接其他线程调用此对象的notify()或notifyAll()方法。


  • notify(): 唤醒在此对象监视器上等待的单个线程


  • notifyAll(): 通知所有等待该竞争资源的线程


注意:当要调用wait()或notify()/notifyAll()方法时,一定要放到synchronized(obj)代码中,否则会报错java.lang.IllegalMonitorStateException。当调用obj.notify/notifyAll后,调用线程依旧持有obj锁,因此等待线程虽被唤醒,但仍无法获得obj锁,直到调用线程退出synchronized块,释放obj锁后,其他等待线程才有机会获得锁继续执行


3.7 join、sleep、wait


  • join()方法在等待的过程中释放对象锁。


  • sleep()方法在睡眠时不释放对象锁,


  • wait():释放对象锁


3.8 线程阻塞


  • 1:线程执行了Thread.sleep(int millsecond)方法,放弃CPU,睡眠一段时间,一段时间过后恢复执行;


  • 2:线程执行一段同步代码,但无法获得相关的同步锁,只能进入阻塞状态,等到获取到同步锁,才能恢复执行;


  • 3:线程执行了一个对象的wait()方法,直接进入阻塞态,等待其他线程执行notify()/notifyAll()操作;


  • 4:线程执行某些IO操作,因为等待相关资源而进入了阻塞态,如System.in,但没有收到键盘的输入,则进入阻塞态。


  • 5:线程礼让,Thread.yield()方法,暂停当前正在执行的线程对象,把执行机会让给相同或更高优先级的线程,但并不会使线程进入阻塞态,线程仍处于可执行态,随时可能再次分得CPU时间。


  • 6:线程自闭,join()方法,在当前线程调用另一个线程的join()方法,则当前线程进入阻塞态,直到另一个线程运行结束,当前线程再由阻塞转为就绪态。


  • 7:线程执行suspend()使线程进入阻塞态,必须resume()方法被调用,才能使线程重新进入可执行状态。


3.9 线程中断


       使用 interrupt()中断,但调用interrupt()方法只是传递中断请求消息,并不代表要立马停止目标线程。然后通过抛出InterruptedException来唤醒它。


public class Thread {
    // 中断当前线程
    public void interrupt();
    // 判断当前线程是否被中断
    public boolen isInterrupt();
    // 清除当前线程的中断状态,并返回之前的值
    public static boolen interrupted();
}


3.10 线程池ThreadPoolExecutor


       线程池的工作原理:线程池可以减少创建和销毁线程的次数,从而减少系统资源的消耗。


当一个任务提交到线程池时:


  • 1:首先判断核心线程池中的线程是否已经满了,如果没满,则创建一个核心线程执行任务,否则进入下一步。


  • 2:判断工作队列是否已满,没有满则加入工作队列,否则执行下一步。


  • 3:判断线程数是否达到了最大值,如果不是,则创建非核心线程执行任务,否则执行饱和策略,默认抛出异常。


3.11 线程池的种类


      FixedThreadPool:可重用固定线程数的线程池,只有核心线程,没有非核心线程,核心线程不会被回收,有任务时,有空闲的核心线程就用核心线程执行,没有则加入队列排队。


      SingleThreadExecutor:单线程线程池,只有一个核心线程,没有非核心线程,当任务到达时,如果没有运行线程,则创建一个线程执行,如果正在运行则加入队列等待,可以保证所有任务在一个线程中按照顺序执行,和FixedThreadPool的区别只有数量。


      CachedThreadPool:按需创建的线程池,没有核心线程,非核心线程有Integer.MAX_VALUE个,每次提交任务如果有空闲线程则由空闲线程执行,没有空闲线程则创建新的线程执行,适用于大量的需要立即处理的并且耗时较短的任务。


      ScheduledThreadPoolExecutor:继承自ThreadPoolExecutor,用于延时执行任务或定期执行任务,核心线程数固定,线程总数为Integer.MAX_VALUE。


3.12 保证线程安全


       线程安全性体现在三个方法:


      原子性:提供互斥访问,同一时刻只能有一个线和至数据进行操作。


       JDK中提供了很多atomic类,如AtomicInteger\AtomicBoolean\AtomicLong,它们是通过CAS完成原子性。 JDK提供锁分为两种:synchronized依赖JVM实现锁,该关键字作用对象的作用范围内同一时刻只能有一个线程进行操作。另一种是LOCK,是JDK提供的代码层面的锁,依赖CPU指令,代表性是ReentrantLock。


     可见性:一个线程对主内存的修改及时被其他线程看到。


       JVM提供了synchronized和volatile,volatile的可见性是通过内存屏障和禁止重排序实现的,volatile会在写操作时,在写操作后加一条store屏障指令,将本地内存中的共享变量值刷新到主内存;会在读操作时,在读操作前加一条load指令,从内存中读取共享变量。


      有序性:指令没有被编译器重排序。


       可通过volatile、synchronized、Lock保证有序性。


3.12 volatile、synchronized、Lock、ReentrantLock


      volatile:解决变量在多个线程间的可见性,但不能保证原子性,只能用于修饰变量,不会发生阻塞。volatile能屏蔽编译指令重排,不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面。多用于并行计算的单例模式。volatile规定CPU每次都必须从内存读取数据,不能从CPU缓存中读取,保证了多线程在多CPU计算中永远拿到的都是最新的值。


      synchronized:互斥锁,操作互斥,并发线程过来,串行获得锁,串行执行代码。解决的是多个线程间访问共享资源的同步性,可保证原子性,也可间接保证可见性,因为它会将私有内存和公有内存中的数据做同步。可用来修饰方法、代码块。会出现阻塞。synchronized发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生。非公平锁,每次都是相互争抢资源。


      lock:一个接口,lock可以让等待锁的线程响应中断。在发生异常时,如果没有主动通过unLock()去释放锁,则可能造成死锁现象,因此使用Lock时需要在finally块中释放锁。


      ReentrantLoc:可重入锁,锁的分配机制是基于线程的分配,而不是基于方法调用的分配。ReentrantLock有tryLock方法,如果锁被其他线程持有,返回false,可避免形成死锁。对代码加锁的颗粒会更小,更节省资源,提高代码性能。ReentrantLock可实现公平锁和非公平锁,公平锁就是先来的先获取资源。ReentrantReadWriteLock用于读多写少的场合,且读不需要互斥场景。


相关推荐


❤️Android Runtime (ART) 和 Dalvik❤️


❤️Android Apk 的打包过程 ❤️


❤️Android Apk的启动过程❤️


❤️startActivity源码分析(含启动新应用) ❤️




相关文章
|
7天前
|
存储 消息中间件 资源调度
「offer来了」进程线程有啥关系?10个知识点带你巩固操作系统基础知识
该文章总结了操作系统基础知识中的十个关键知识点,涵盖了进程与线程的概念及区别、进程间通信方式、线程同步机制、死锁现象及其预防方法、进程状态等内容,并通过具体实例帮助理解这些概念。
「offer来了」进程线程有啥关系?10个知识点带你巩固操作系统基础知识
|
23天前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android多线程编程的重要性及其实现方法,涵盖了基本概念、常见线程类型(如主线程、工作线程)以及多种多线程实现方式(如`Thread`、`HandlerThread`、`Executors`、Kotlin协程等)。通过合理的多线程管理,可大幅提升应用性能和用户体验。
41 15
一个Android App最少有几个线程?实现多线程的方式有哪些?
|
6天前
|
资源调度 算法 调度
深入浅出操作系统之进程与线程管理
【9月更文挑战第29天】在数字世界的庞大舞台上,操作系统扮演着不可或缺的角色,它如同一位精通多门艺术的导演,精心指挥着每一个进程和线程的演出。本文将通过浅显的语言,带你走进操作系统的内心世界,探索进程和线程的管理奥秘,让你对这位幕后英雄有更深的了解。
|
10天前
|
Java
直接拿来用:进程&进程池&线程&线程池
直接拿来用:进程&进程池&线程&线程池
|
11天前
|
负载均衡 Java 调度
探索Python的并发编程:线程与进程的比较与应用
本文旨在深入探讨Python中的并发编程,重点比较线程与进程的异同、适用场景及实现方法。通过分析GIL对线程并发的影响,以及进程间通信的成本,我们将揭示何时选择线程或进程更为合理。同时,文章将提供实用的代码示例,帮助读者更好地理解并运用这些概念,以提升多任务处理的效率和性能。
|
16天前
|
Java Android开发 UED
🧠Android多线程与异步编程实战!告别卡顿,让应用响应如丝般顺滑!🧵
在Android开发中,为应对复杂应用场景和繁重计算任务,多线程与异步编程成为保证UI流畅性的关键。本文将介绍Android中的多线程基础,包括Thread、Handler、Looper、AsyncTask及ExecutorService等,并通过示例代码展示其实用性。AsyncTask适用于简单后台操作,而ExecutorService则能更好地管理复杂并发任务。合理运用这些技术,可显著提升应用性能和用户体验,避免内存泄漏和线程安全问题,确保UI更新顺畅。
38 5
|
21天前
|
开发者 Python
深入浅出操作系统:进程与线程的奥秘
【8月更文挑战第46天】在数字世界的幕后,操作系统扮演着至关重要的角色。本文将揭开进程与线程这两个核心概念的神秘面纱,通过生动的比喻和实际代码示例,带领读者理解它们的定义、区别以及如何在编程中运用这些知识来优化软件的性能。无论你是初学者还是有一定经验的开发者,这篇文章都将为你提供新的视角和实用技巧。
|
25天前
|
Java 数据库 Android开发
一个Android App最少有几个线程?实现多线程的方式有哪些?
本文介绍了Android应用开发中的多线程编程,涵盖基本概念、常见实现方式及最佳实践。主要内容包括主线程与工作线程的作用、多线程的多种实现方法(如 `Thread`、`HandlerThread`、`Executors` 和 Kotlin 协程),以及如何避免内存泄漏和合理使用线程池。通过有效的多线程管理,可以显著提升应用性能和用户体验。
38 10
|
23天前
|
Java Android开发 数据安全/隐私保护
Android中多进程通信有几种方式?需要注意哪些问题?
本文介绍了Android中的多进程通信(IPC),探讨了IPC的重要性及其实现方式,如Intent、Binder、AIDL等,并通过一个使用Binder机制的示例详细说明了其实现过程。
120 4
|
23天前
|
API Android开发 iOS开发
安卓与iOS开发中的线程管理对比
【9月更文挑战第12天】在移动应用的世界中,安卓和iOS平台各自拥有庞大的用户群体。开发者们在这两个平台上构建应用时,线程管理是他们必须面对的关键挑战之一。本文将深入探讨两大平台在线程管理方面的异同,通过直观的代码示例,揭示它们各自的设计理念和实现方式,帮助读者更好地理解如何在安卓与iOS开发中高效地处理多线程任务。