Java 线程

1、线程是什么?
  • 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
  • 一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。
  • 通俗来说,线程是 CPU 调度的最小单位,一个进程中可以包含多个线程,在 Android 中,一个进程通常是一个 App,App 中会有一个主线程,主线程可以用来操作界面元素,如果有耗时的操作,必须开启子线程执行,不然会出现 ANR,除此以外,进程间的数据是独立的,线程间的数据可以共享。
  • ANR:在 Android 上,如果你的应用有一段时间响应不够灵敏,系统会向用户显示一个对话框,这个对话框称为应用程序无响应对话框,也就是 ANR,不同的组件发生ANR的时间不一样,Activity 是 5 秒,BroadCastReceiver 是 10 秒,Service 是 20 秒(均为前台)
  • 如何解决: 找到奔溃的地方 ——> 获取堆栈信息
    ——> 如果看不到异常 Exception 日志,可能就是 ANR
    ——> 从堆栈中获取信息(经由页面)
    ——> 分析各个页面中的耗时操作,然后处理
2、基础概念
  • 并行:多个 CPU 实例或多台机器同时执行一段代码,是真正的同时。
  • 并发:通过 CPU调度算法,让用户看上去同时执行,实际上从 CPU 操作层面不是真正的同时。
  • 线程安全:指在并发的情况之下,该代码经过多线程使用,线程的调度顺序不影响任何结果。线程不安全就意味着线程的调度顺序会影响最终结果,比如某段代码不加事务去并发访问。
  • 线程同步:指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。如某段代码加入 synchronized 关键字。线程安全的优先级高于性能优化。
  • 原子性:一个操作或者一系列操作,要么全部执行要么全部不执行。数据库中的“事物”就是个典型的原子操作。
  • 可见性:当一个线程修改了共享属性的值,其它线程能立刻看到共享属性值的更改。比如 JMM 分为主存和工作内存,共享属性的修改过程是在主存中读取并复制到工作内存中,在工作内存中修改完成之后,再刷新主存中的值。若线程 A 在工作内存中修改完成但还来得及刷新主存中的值,这时线程 B 访问该属性的值仍是旧值。这样可见性就没法保证。
  • 有序性:程序运行时代码逻辑的顺序在实际执行中不一定有序,为了提高性能,编译器和处理器都会对代码进行重新排序。前提是,重新排序的结果要和单线程执行程序顺序一致。
3、线程的生命周期
  • 一个线程完整的生命周期转换图,如下所示:
    线程生命周期转换图
  • 新建状态( New ):使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
  • 就绪状态( Runnable ):当线程对象调用了 start() 方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待 JVM 里线程调度器的调度。
  • 运行状态( Running ):如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
  • 阻塞状态( Blocked ):如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
    • 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。该线程会释放占用的所有资源,JVM会把该线程放入“等待队列”中。进入这个状态后,是不能自动唤醒的必须依靠其他线程调用 notify() 或 notifyAll() 方法才能被唤醒。
    • 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用),则JVM会把该线程放入“锁池”中。
    • 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当 sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
  • 死亡状态( Dead ):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
4、线程的优先级
  • 每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。
  • 注意:具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。
5、线程管理
  • sleep():使一个线程处于睡眠(阻塞)状态,属于 Thread 类中的。特点:调用此方法过程中线程不会释放对象锁。它会导致程序暂停执行指定的时间,让出 cpu 给其他线程,但是它仍然保持对 Thread 的监控状态,直到指定的时间结束后又会自动恢复可运行状态( Runnable )。
  • join():等待该线程结束,才能执行其他线程,属于 Thread 类中的。通常用于在 main() 主线程内,等待其它线程完成再执行 main() 主线程。join 方法实现原理是通过 wait 方法, 当 main 线程调用 Thread.join() 时,main 线程会获得线程对象的锁,调用该对象的 wait(),直到该对象唤醒 main 线程 。这就意味着 main 线程调用 Thread.join 时,必须能够拿到线程对象的锁。
  • yield():当线程在 runnable 可运行状态时是处于被调度的线程,此时的调度顺序是不一定的。 Thread 类中的 yield() 方法可以让一个 running 状态的线程转入 runnable。
  • wait():使一个线程处于等待状态,属于 Object类 中的。特点:会释放持有的对象锁,进入等待此对象的锁定池( lock blocked pool ),直到针对此对象调用 notify()/notifyAll() 被唤醒,释放同步锁使线程回到可运行状态( Runnable )。
  • notify():使一个等待状态的线程唤醒。注意:并不能确切唤醒等待状态线程,是由 JVM 决定且不按优先级。
  • notifyAll():使所有等待状态的线程唤醒。注意:并不是给所有线程上锁,而是让它们竞争。
  • synchronized关键字:可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。使 Running 状态的线程加同步锁使其进入锁定池( lock blocked pool )。同步锁被释放后进入可运行状态( Runnable )。缺点是无法中断线程,也无法设置阻塞时长。
5.1 在线程中 Object#wait() 和 Thread#sleep(xxx) 方法的区别?
  • Object#wait() 方法既释放 cpu,又释放锁。
  • Thread#sleep(xxx) 方法只释放 cpu,但是不释放锁。
6、线程实现的三种方式
  • 继承 Thread 类实现多线程。
  • 实现 Runnable 接口方式实现多线程。
  • 实现 Callable 接口 + FutureTask包装器方式实现多线程。
6.1 继承 Thread 类实现多线程优劣势:
  • 优势:编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。
  • 劣势:由于 Java 是单继承,所以如果线程类已经继承了 Thread 类,就不能再继承其它父类。
6.2 实现 Runnable 或 Callable 接口方式实现多线程优劣势:
  • 优势:线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。在这种方式下,多个线程可以共享同一个 target 对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将 CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
  • 劣势:编程稍微复杂,如果要访问当前线程,则必须使用 Thread.currentThread() 方法。
6.3 注意事项:
  • 在通过实现 Callable 接口方式实现多线程过程中,Thread#start() 方法只能执行一次,即开启线程只能执行一次。而如果直接调用 Thread#run() 则该方法是在被调用线程执行。
  • 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值,调用 get() 方法会阻塞线程。
6.4 示例代码:
public class MultiThreadDemo {

    public static void main(String[] args) {
        // 继承 Thread 类实现多线程
        MyThread myThread1_1 = new MyThread();
        MyThread myThread1_2 = new MyThread();
        myThread1_1.start();
        myThread1_2.start();
        // 实现 Runnable 接口方式实现多线程
        MyRunnable myRunnable = new MyRunnable();
        new Thread(myRunnable).start();
        new Thread(myRunnable).start();
        // 实现 Callable 接口 +  FutureTask包装器方式实现多线程
        MyCallable callable = new MyCallable("Hello 多线程");
        FutureTask<Integer> task1 = new FutureTask<Integer>(callable);
        new Thread(task1).start();
        FutureTask<Integer> task2 = new FutureTask<Integer>(callable);
        new Thread(task2).start();
    }

    /**
     * 第一种方式:继承 Thread 类实现多线程
     */
    static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("继承 Thread 类实现多线程=>" + this.getName() + "运行");
        }
    }

    /**
     * 第二种方式:实现 Runnable 接口方式实现多线程
     */
    static class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("实现 Runnable 接口方式实现多线程=>" + Thread.currentThread().getName() + "运行");
        }
    }

    /**
     * 第三种方式:实现 Callable 接口 +  FutureTask包装器方式实现多线程
     */
    static class MyCallable implements Callable {

        private String str;

        public MyCallable(String str) {
            this.str = str;
        }

        @Override
        public Boolean call() throws Exception {
            System.out.println("实现 Callable 接口 +  FutureTask包装器方式实现多线程=>" + Thread.currentThread().getName() + "运行,传递的值为:" + str);
            return true;
        }
    }
}
7、线程中断的方式
  • 使用 violate boolean 变量标志位来退出线程。(推荐)
  • 使用 interrupt() 方法中断线程,但是线程不一定会终止。因为如果线程被 Object.wait、Thread.join 和 Thread.sleep 三种方法之一阻塞,此时调用该线程的 interrupt() 方法,那么该线程将抛出一个 InterruptedException 中断异常(该线程必须事先预备好处理此异常),从而提早地终结被阻塞状态。如果线程没有被阻塞,这时调用 interrupt() 将不起作用,直到执行到 wait()、sleep()、join() 时,才马上会抛出 InterruptedException 。
  • 使用 stop 方法强行终止线程。(被弃用)(1)调用 stop() 方法会立刻停止 run() 方法中剩余的全部工作,包括在 catch 或 finally 语句中的,并抛出 ThreadDeath 异常(通常情况下此异常不需要显示的捕获),因此可能会导致一些清理性的工作的得不到完成,如文件,数据库等的关闭。(2)调用 stop() 方法会立即释放该线程所持有的所有的锁,导致数据得不到同步,出现数据不一致的问题。
public class ExitThreadDemo {

    public static void main(String[] args) {
        exitThread_1();
        exitThread_2();
    }

    public static volatile boolean isThreadExit = false;  //退出标志
    private static void exitThread_1() {
        new Thread() {
            public void run() {
                System.out.println("线程1启动了");
                while (true) {
                    if (!isThreadExit) {
                        try {
                            System.out.println("线程1运行中...");
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        System.out.println("线程1结束了");
                        // break配合中断线程1
                        break;
                    }
                }
            }
        }.start();
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 5秒后更改退出标志的值,没有这段代码,线程就一直不能停止
        isThreadExit = true;
    }

    private static void exitThread_2() {
        Thread thread = new Thread() {
            public void run() {
                System.out.println("线程2启动了");
                while (true) {
                    try {
                        System.out.println("线程2运行中...");
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        System.out.println("线程2结束了");
                        // retrun配合中断线程2
                        return;
                    }
                }
            }
        };
        thread.start();
        try {
            Thread.sleep(1000 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 5秒后更改退出标志的值,没有这段代码,线程就一直不能停止
        thread.interrupt();
    }
}
8、线程的几个主要概念
  • 线程同步
  • 线程间通信
  • 线程死锁
  • 线程控制:挂起、停止和恢复
  • 多线程的使用注意事项:有效利用多线程的关键是理解程序是并发执行而不是串行执行的。例如:程序中有两个子系统需要并发执行,这时候就需要利用多线程编程。通过对多线程的使用,可以编写出非常高效的程序。不过请注意,如果你创建太多的线程,程序执行的效率实际上是降低了,而不是提升了。请记住,上下文的切换开销也很重要,如果你创建了太多的线程,CPU 花费在上下文的切换的时间将多于执行程序的时间!
9、Android 线程间通信方式
  • (1)Handler
  • (2)Activity.runOnUiThread(Runnable)
  • (3)View.post(Runnable)/View.postDelayed(Runnable,long)
  • (4)AsyncTask
  • 等等…
10、Android 进程间通信方式
  • (1)Bundle:由于 Activity,Service,Receiver 都是可以通过 Intent 来携带 Bundle 传输数据的,所以我们可以在一个进程中通过 Intent 将携带数据的 Bundle 发送到另一个进程的组件。但是有一个缺点,就是无法传输 Bundle 不支持的数据类型。
  • (2)ContentProvider:ContentProvider 是 Android 四大组件之一,以表格的方式来储存数据,提供给外界,即 Content Provider 可以跨进程访问其他应用程序中的数据。用法是继承 ContentProvider,实现 onCreate,query,update,insert,delete 和 getType 方法,onCreate 是负责创建时做一些初始化的工作,增删查改的方法就是对数据的查询和修改,getType 是返回一个 String,表示 Uri 请求的类型。注册完后就可以使用 ContentResolver 去请求指定的 Uri。
  • (3)文件:两个进程可以到同一个文件去交换数据,我们不仅可以保存文本文件,还可以将对象持久化到文件,从另一个文件恢复。要注意的是,当并发读/写时可能会出现并发的问题。
  • (4)Broadcast:Broadcast 可以向 Android 系统中所有应用程序发送广播,而需要跨进程通讯的应用程序可以监听这些广播。
  • (5)AIDL方式:Service 和 Content Provider 类似,也可以访问其他应用程序中的数据,Content Provider 返回的是 Cursor 对象,而 Service 返回的是 Java 对象,这种可以跨进程通讯的服务叫 AIDL 服务。AIDL 通过定义服务端暴露的接口,以提供给客户端来调用,AIDL 使服务器可以并行处理,而 Messenger 封装了 AIDL 之后只能串行运行,所以 Messenger 一般用作消息传递。
  • (6)Messenger:Messenger 是基于 AIDL 实现的,服务端(被动方)提供一个 Service 来处理客户端(主动方)连接,维护一个 Handler 来创建 Messenger,在 onBind 时返回 Messenger 的 binder。双方用 Messenger 来发送数据,用 Handler 来处理数据。Messenger 处理数据依靠 Handler,所以是串行的,也就是说,Handler 接到多个 message 时,就要排队依次处理。
  • (7)Socket:Socket 方法是通过网络来进行数据交换,注意的是要在子线程请求,不然会堵塞主线程。客户端和服务端建立连接之后即可不断传输数据,比较适合实时的数据传输。
  • 参考文献:https://cloud.tencent.com/developer/article/1441385
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目标检测(Object Detection)是计算机视觉领域的一个核心问题,其主要任务是找出图像中所有感兴趣的目标(物体),并确定它们的类别和位置。以下是对目标检测的详细阐述: 一、基本概念 目标检测的任务是解决“在哪里?是什么?”的问题,即定位出图像中目标的位置并识别出目标的类别。由于各类物体具有不同的外观、形状和姿态,加上成像时光照、遮挡等因素的干扰,目标检测一直是计算机视觉领域最具挑战性的任务之一。 二、核心问题 目标检测涉及以下几个核心问题: 分类问题:判断图像中的目标属于哪个类别。 定位问题:确定目标在图像中的具体位置。 大小问题:目标可能具有不同的大小。 形状问题:目标可能具有不同的形状。 三、算法分类 基于深度学习的目标检测算法主要分为两大类: Two-stage算法:先进行区域生成(Region Proposal),生成有可能包含待检物体的预选框(Region Proposal),再通过卷积神经网络进行样本分类。常见的Two-stage算法包括R-CNN、Fast R-CNN、Faster R-CNN等。 One-stage算法:不用生成区域提议,直接在网络中提取特征来预测物体分类和位置。常见的One-stage算法包括YOLO系列(YOLOv1、YOLOv2、YOLOv3、YOLOv4、YOLOv5等)、SSD和RetinaNet等。 四、算法原理 以YOLO系列为例,YOLO将目标检测视为回归问题,将输入图像一次性划分为多个区域,直接在输出层预测边界框和类别概率。YOLO采用卷积网络来提取特征,使用全连接层来得到预测值。其网络结构通常包含多个卷积层和全连接层,通过卷积层提取图像特征,通过全连接层输出预测结果。 五、应用领域 目标检测技术已经广泛应用于各个领域,为人们的生活带来了极大的便利。以下是一些主要的应用领域: 安全监控:在商场、银行
目标检测(Object Detection)是计算机视觉领域的一个核心问题,其主要任务是找出图像中所有感兴趣的目标(物体),并确定它们的类别和位置。以下是对目标检测的详细阐述: 一、基本概念 目标检测的任务是解决“在哪里?是什么?”的问题,即定位出图像中目标的位置并识别出目标的类别。由于各类物体具有不同的外观、形状和姿态,加上成像时光照、遮挡等因素的干扰,目标检测一直是计算机视觉领域最具挑战性的任务之一。 二、核心问题 目标检测涉及以下几个核心问题: 分类问题:判断图像中的目标属于哪个类别。 定位问题:确定目标在图像中的具体位置。 大小问题:目标可能具有不同的大小。 形状问题:目标可能具有不同的形状。 三、算法分类 基于深度学习的目标检测算法主要分为两大类: Two-stage算法:先进行区域生成(Region Proposal),生成有可能包含待检物体的预选框(Region Proposal),再通过卷积神经网络进行样本分类。常见的Two-stage算法包括R-CNN、Fast R-CNN、Faster R-CNN等。 One-stage算法:不用生成区域提议,直接在网络中提取特征来预测物体分类和位置。常见的One-stage算法包括YOLO系列(YOLOv1、YOLOv2、YOLOv3、YOLOv4、YOLOv5等)、SSD和RetinaNet等。 四、算法原理 以YOLO系列为例,YOLO将目标检测视为回归问题,将输入图像一次性划分为多个区域,直接在输出层预测边界框和类别概率。YOLO采用卷积网络来提取特征,使用全连接层来得到预测值。其网络结构通常包含多个卷积层和全连接层,通过卷积层提取图像特征,通过全连接层输出预测结果。 五、应用领域 目标检测技术已经广泛应用于各个领域,为人们的生活带来了极大的便利。以下是一些主要的应用领域: 安全监控:在商场、银行
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值