前言
在整个Android学习生涯中,回忆起来,有哪些知识是我们记忆深刻的,已经深入骨髓?启动的目标Activity必须在清单文件中注册,请求网络必须添加网络权限,又或者其它权限在6.0以后必须动态申请,etc...我想这些都是每个Android人都已经刻在骨子里了。仔细想想,你是否又想起了,主线程不能操作耗时任务,对,你激动的拍了一下自己的大腿,就是这个卵“主线程不能操作耗时任务”,是的,尤其在我们初学Android的时候,会经常犯的毛病,但我们经常使用的线程究竟是怎么一个东西,我们是否有过深入思考,又或者我们是否思考过,我们有没有正确使用线程,在以往使用的过程中是否可以再进一步优化,etc...带着这一系列疑问,还是有必要深入了解一下Android中的多线程。为了彻底整明白这些知识,还是从java线程基础开始。
什么是进程和线程
1,什么是进程?
进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。进程是一种抽象的概念,从来没有统一的标准定义。
进程一般由程序、数据集合和进程控制块三部分组成。这些太官方,以正在使用的windows操作系统为例,打开任务管理器,我们就可以清楚的知道进程究竟是什么:
我们可以清楚的看到,目前操作系统一共起了94个进程,有我们开启的10个应用,和操作系统自己起的84个后台进程正在运行。我们通常会说一个应用就是一个进程,其实不太准确,因为一个应用只有跑起来后才会有一个活着的进程。
2,什么是线程?
线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。一个标准的线程由线程ID、当前指令指针(PC)、寄存器和堆栈组成。而进程由内存空间(代码、数据、进程空间、打开的文件)和一个或多个线程组成。简单理解就是线程是用来执行任务的。
从上图中,可以清楚知道wps下面就有7个线程。简单的说就是一个进程下面包含多个线程。
3,任务调度器
线程是什么?要理解这个概念,需要先了解一下操作系统的一些相关概念。大部分操作系统(如Windows、Linux)的任务调度是采用时间片轮转的抢占式调度方式,也就是说一个任务执行一小段时间后强制暂停去执行下一个任务,每个任务轮流执行。任务执行的一小段时间叫做时间片,任务正在执行时的状态叫运行状态,任务执行一段时间后强制暂停去执行下一个任务,被暂停的任务就处于就绪状态等待下一个属于它的时间片的到来。这样每个任务都能得到执行,由于CPU的执行效率非常高,时间片非常短,在各个任务之间快速地切换,给人的感觉就是多个任务在“同时进行”,这也就是我们所说的并发(别觉得并发有多高深,它的实现很复杂,但它的概念很简单,就是一句话:多个任务同时执行)。多任务运行过程的示意图如下:
抛开上面的图以及概念性东西,在我们实际编程中,假如同时起了A,B,C三个线程执行一些任务,是不是每个线程完成的顺序都是随机的,这就应对了上面的时间片轮转机制。
4,进程与线程的关系
地址空间:
线程共享本进程的地址空间,而进程之间是独立的地址空间。
资源:
线程共享本进程的资源如内存、I/O、cpu等,不利于资源的管理和保护,而进程之间的资源是独立的,能很好的进行资源管理和保护。
健壮性:
多进程要比多线程健壮,一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。
可并发性:
两者均可并发执行。
切换时:
进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程不能用进程。
其他:
线程是处理器调度的基本单位,但是进程不是。
我们先用下面这张图,也许会更直观:
5,单线程和多线程
总之,线程和进程都是一种抽象的概念,线程是一种比进程更小的抽象,线程和进程都可用于实现并发。
在早期的操作系统中并没有线程的概念,进程是能拥有资源和独立运行的最小单位,也是程序执行的最小单位。它相当于一个进程里只有一个线程,进程本身就是线程。所以线程有时被称为轻量级进程(Lightweight Process,LWP)。
我们试想一下,如果没有多线程,我们一个app只有一个主线程,它需要渲染UI和请求网络,我想我们是无法忍受这样一个过程的。
cpu核心数和线程的关系
在配置电脑的时候,就会关注一点我们电脑是几核几线程,cpu核心数和线程有什么关系呢?
上面提到的时间片轮转的调度方式说一个任务执行一小段时间后强制暂停去执行下一个任务,每个任务轮流执行。很多操作系统的书都说“同一时间点只有一个任务在执行”。那有人可能就要问双核处理器呢?难道两个核不是同时运行吗?
其实“同一时间点只有一个任务在执行”这句话是不准确的,至少它是不全面的。那多核处理器的情况下,线程是怎样执行呢?这就需要了解内核线程。
多核(心)处理器是指在一个处理器上集成多个运算核心从而提高计算能力,也就是有多个真正并行计算的处理核心,每一个处理核心对应一个内核线程。
内核线程(Kernel Thread,KLT)就是直接由操作系统内核支持的线程,这种线程由内核来完成线程切换,内核通过操作调度器对线程进行调度,并负责将线程的任务映射到各个处理器上。一般一个处理核心对应一个内核线程,比如单核处理器对应一个内核线程,双核处理器对应两个内核线程,四核处理器对应四个内核线程。
现在的电脑一般是双核四线程、四核八线程,是采用超线程技术将一个物理处理核心模拟成两个逻辑处理核心,对应两个内核线程,所以在操作系统中看到的CPU数量是实际物理CPU数量的两倍,如你的电脑是双核四线程,打开“任务管理器\性能”可以看到4个CPU的监视器,四核八线程可以看到8个CPU的监视器。
四核八线程在Windows10下查看的结果
超线程技术就是利用特殊的硬件指令,把一个物理芯片模拟成两个逻辑处理核心,让单个处理器都能使用线程级并行计算,进而兼容多线程操作系统和软件,减少了CPU的闲置时间,提高的CPU的运行效率。这种超线程技术(如双核四线程)由处理器硬件的决定,同时也需要操作系统的支持才能在计算机中表现出来。
程序一般不会直接去使用内核线程,而是去使用内核线程的一种高级接口——轻量级进程(Lightweight Process,LWP),轻量级进程就是我们通常意义上所讲的线程,也被叫做用户线程。由于每个轻量级进程都由一个内核线程支持,因此只有先支持内核线程,才能有轻量级进程。用户线程与内核线程的对应关系有三种模型:一对一模型、多对一模型、多对多模型,在这以4个内核线程、3个用户线程为例对三种模型进行说明。
java中的多线程
1,java中线程的创建
网上回答这个问题有各种答案,我们还是翻一下java jdk源码提供的注释,感觉有的人真是误人子弟:
There are two ways to create a new thread of execution. One is to declare a class to be a subclass of Thread. This subclass should override the run method of class Thread. An instance of the subclass can then be allocated and started. For example, a thread that computes primes larger than a stated value could be written as follows:
class PrimeThread extends Thread {
long minPrime;
PrimeThread(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
The following code would then create a thread and start it running:
PrimeThread p = new PrimeThread(143);
p.start();
The other way to create a thread is to declare a class that implements the Runnable interface. That class then implements the run method. An instance of the class can then be allocated, passed as an argument when creating Thread, and started. The same example in this other style looks like the following:
class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
The following code would then create a thread and start it running:
PrimeRun p = new PrimeRun(143);
new Thread(p).start();
Every thread has a name for identification purposes. More than one thread may have the same name. If a name is not specified when a thread is created, a new name is generated for it.
Unless otherwise noted, passing a null argument to a constructor or method in this class will cause a NullPointerException to be thrown.
Since:
JDK1.0
See Also:
Runnable, Runtime.exit(int), run(), stop()
Author:
unascribed
所以建议那些网上乱说一通的,记得看一下jdk源码提供的注释。
总结
上面我们主要介绍了进程和线程的一些基本概念,因为面试基本是必问的知识点,最后也拿了jdk源码中的注释来说明线程的两种创建方式,尤其是线程的两种创建方式,面试问的太多了,所以当我们知识点存在盲区的时候翻看一下源码是最好的解决办法,网上人云亦云的太多了,乱七八糟。在后面我们也将全面复习java中的多线程知识点,如线程生命周期,线程间通信,以及在Android中的应用等。