前言
想必大家对电脑都不陌生,不管你的电脑是Windows系统还是Mac系统,现在都已经是多任务操作系统,多任务就是可以在同一时间运行多个程序的能力。多任务带给我们的最直观的体验,就是我们可以边听音乐边打字。 不过多任务操作系统不一定要有多CPU,让多个任务分时共享CPU就好了。比如Windows多任务处理采用的是虚拟机技术。而且要注意的是单核的多任务操作系统在宏观上(看起来)是并行的,微观上(程序运行时)是并发的。(有空聊聊多核、单核、多CPU?)
什么是线程?
在引入线程前我们先来了解下什么是进程,因为线程是进程中的一个实体,线程本身是不会独立存在的。
- 进程:进程是代码在数据集合上的一次运行活动,是一个动态概念。它是系统进行资源分配和调度的基本单位,每个进程都拥有自己的一整套变量(地址空间)。进程的五种基本状态:初始态、执行态、等待状态、就绪状态、终止状态。
- 线程:线程是进程的一个执行路径,一个进程至少有一个线程,进程中的多个线程共享进程的资源。线程是CPU分配的基本单位。
这里我们抽象的来理解下,我们可以将进程比作是一场正在进行的演唱会,而线程就是歌手、吉他手、鼓手等表演人员,台下的观众就是资源。
- 【一个进程包含至少一个进程】:当演奏一首歌时,歌手在唱歌,吉他手在弹吉他,鼓手在敲鼓。当然歌手可以选择清唱。
- 【多个线程共享进程的资源】:一场演唱会的观众是固定的,因此表演人员们所能获得到的关注度是一定的,我们这里假定每个观众都只能关注一个自己喜欢的,比如歌手或者吉他手。
总结:进程是资源分配的基本单位,线程是CPU分配的基本单位。(也可以说是最小单位)
当然我们可以从更专业的角度来理解进程和线程之间的关系:(以下内容参考自《Java并发编程之美》、《深入理解Java虚拟机》)
在Java中,当我们启动main函数时,其实就是启动了一个JVM进程,而main函数所在的线程就是这个进程中的一个进程,也称主线程。

从上图我们可以看到,一个进程中可以有多个线程,多个线程共享进程的堆和方法区资源,但是每个线程都有自己的程序计数器和栈区域。
- 程序计数器:下一条指令地址。作用就是记录当前线程让出CPU时的执行地址,等到再次分配到CPU时间片时线程基于可以从自己私有的计数器指定地址继续执行。简单一句话,就是记录之前程序执行到哪里了。(可以理解为轮盘->)
- 栈:每个线程都有自己的栈资源,用于存储该线程的局部变量,这些局部变量是该线程私有的,还可以存放线程的调用栈帧。
- 堆:是一个进程中最大的一块内存,堆是被进程中的所有线程共享的,是进程创建时分配的。我们在讲类和对象时提到过,几乎所有的对象实例和数组都是在堆中分配内存的。
- 方法区:方法区和Java堆一样,也是各个线程共享变量的内存区域,它用于存放已被JVM加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。《Java虚拟机规范》中把方法区描述为堆的一个逻辑部分,但是他也有一个别名叫做“非堆”(Non-Heap),目的是与Java堆区分开来。
Java线程的创建方式
常见的Java线程的3种创建方式:继承Thread类、实现Runnable接口、和Callable实现有返回值的线程。

继承Thread类
代码语言:javascript复制public class Algorithm {
static int i=0;
static class MyThread extends Thread {
private String name;
public MyThread(){};
public MyThread(String name){
this.name = name;
}
@Override
public void run() {
for(int k=0;k<=100;k ) {
System.out.println(name " " k);
}
}
}
public static void main(String[] args) {
MyThread thread1 = new MyThread("线程1");
MyThread thread2 = new MyThread("线程2");
thread1.start();
thread2.start();
}
}
从下图的执行结果中可以看出,线程的调度和先后是没关系的,而且线程1、2是交替执行的。

实现Runnable接口
代码语言:javascript复制public class Run {
static class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("Hello");
}
}
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
new Thread(runnable).start();
}
}
从上述代码我们可以看出,Runnable的实现依赖于Thread。当一个实现了Runnable的线程实例给Thread后,Threa会调用Runnable的run方法。

实现Callable接口
代码语言:javascript复制public class Call {
static class MyCallable implements Callable<String>{
@Override
public String call() throws Exception {
return "Hello";
}
}
public static void main(String[] args) {
FutureTask<String> futureTesk = new FutureTask(new MyCallable());
new Thread(futureTesk).start();
try{
String result = futureTesk.get();
System.out.println(result);
}catch (ExecutionException | InterruptedException e){
e.printStackTrace();
}
}
}
这是一个具有返回值的线程实现方式。

END


