第一章:多线程原理和实践
本文章是 极致经典《Java高并发核心编程》卷二的读书笔记,很不错的一本书
- 进程和线程
- 进程的原理
- 线程的原理
- 进程和线程的区别
- 线程的核心原理
- 线程的调度和时间片
- 线程的优先级
- 线程的生命周期
- 创建线程的4种方式
- 创建线程的方式一:继承Thread类
- 创建线程的方式一:实现Runnable接口创建线程目标类
- 创建线程的方式三:使用Callable和FutureTask创建线程
- 创建线程的方式三:通过线程池创建线程
1.进程和线程
CPU承担了计算机的所有计算任务,内存资源承担了运行时数据的保存任务,外存资源承担了数据外部永久存储的任务。其中任务的调度、资源的分配全由操作系统来统领。应用程序以进程的形式运行于OS上,享受着OS提供的服务
1.1进程的基本原理
- 进程组成的三部分:程序段、数据段和进程控制块(PCB)
- 程序段,即代码段,是进程的程序指令在内存中的位置,包含需要执行的指令集合
- 数据段:进程的操作数据在内存的位置
- PCB由描述信息和控制信息,是进程的唯一标识
什么是Java程序的进程?
Java编写的程序都运行在JVM中,每当使用java命令启动一个java应用程序,就会启动一个JVM进程。在这个JVM进程中,所有java程序代码都是以线程形式运行。
JVM首先找到程序的入口main()方法,然后运行main()方法,就会产生一个线程,即主线程和一个GC(垃圾回收线程),当主线程结束,JVM进程也随即结束。
1.2线程的基本原理
早期OS只有进程没有线程,当时的进程作为程序执行和系统进行并发调度的最小单位。随计算机发展,CPU的性能越来越高,进程调度过于笨重,于是进程内部演进出并发调度的线程
为什么在进程中需要线程:同一个进程中,能够更好的地并行处理。Java程序的进程的执行过程就是多线程的执行过程。
- 线程是进程代码段的一次执行,是程序执行的最小单位,也是CPU调度的基本单位
- 进程则是CPU资源分配的最小单位
- 一个线程由三部分组成:线程基本信息、程序计数器和栈内存
- 线程描述信息:线程ID、名称、优先级、线程状态。。
- 程序计数器:当前线程下一段指令代码的内存地址 (上下文切换时用于场地恢复)
- 栈内存:线程独占内存,每一个方法的运行都会生成一个栈帧,用于存储局部变量表、操作数栈、动态链接和方法返回地址(方法出口)
1.3进程和线程的区别
- 线程是“进程代码块”的一次顺序执行流程。一个进程由一个或多个线程组成
- 线程是Cpu调度的最小单位,也是程序执行的基本单位;进程是CPU资源分配的基本单位。因此线程的划分程度小于进程,使得多线程程序的并发性高
- 进程间相互独立,但同一个进程内的各个线程之间不完全独立。像线程共享的内存:堆区、方法区、系统资源。。
- 线程间的切换速度快于进程间的切换速度
2.线程的核心原理
2.1线程的调度和时间片
目前OS中主流的线程调度方式为:基于时间片的方式进行线程调度。线程得到CPU时间片才能执行指令,处于运行状态。没有得到CPU时间片的线程处于就绪状态,等待OS分配下一个CPU时间片。由于时间片的时间非常短,在各个线程之间快速切换,因此表现出多个线程在“同时执行”或“并发执行”
线程的调度模型主要分为:分时调度模型和抢占调度模型 (通过CPU调度算法衍生的调度模型)
- 分时调度:系统平均分配CPU时间片,所有线程轮流占用CPU
- 抢占式调度:系统按照优先级分配CPU时间片。优先级高的线程优先获得CPU时间片。优先级高并非一定在优先级低的线程之前先运行,优先级高只能说是抢占到CPU执行权的几率大
java的线程调度模型是抢占式调度模型,所以java的线程都有优先级
CPU调度算法:
- 先来先服务
- 短作业优先
- 优先级调度算法
2.2 线程的优先级
Thread类由对应的属性和方法用于进行优先级相关的操作
优先级高并非一定在优先级低的线程之前先运行,优先级高只能说是抢占到CPU执行权的几率大
private int priority; //该属性保存Thread实例的优先级,默认为5,可设置区间为 1-10 值越大优先级越高
public final void setPriority(int newPriority); //设置优先级
public final int getPriority(); //获取优先级
2.3线程的生命周期
关于线程状态转换函数的请参照:
线程生命周期可分为6种状态。Thread类中一个实例属性和一个实例方法专门保存和获取线程状态
private volatile int threadStatus = 0;
public State getState();
public enum State {
NEW, //新建
RUNNABLE, //可执行,包括就绪、运行两种状态
BLOCKED, //阻塞
WAITING, //等待
TIMED_WAITING, //限时等待
TERMINATED; //终止状态
}
2.3.1NEW新建状态
指线程创建成功但没有调用start()方法,线程一经调用start()方法线程就会从NEW状态转变为Runnable的就绪状态
2.3.2Runnable就绪和运行状态
2.3.3TERMINATED 终止状态
转变为TERMINATED终止状态的情况‘
- 在run()方法执行完成之后,就变为TERMINATED
- 在run()方法执行过程中发生了运行时异常而没有捕获,run()方法将被异常终止
2.3.4TIMED_WAITING限时等待状态
能让线程处于线程限时等待状态,
3.线程的创建方式
3.1继承Thread
3.1.1 Thread类详解
一个线程在Java中使用一个Thread实例来描述。Thread类有用于存储和操作线程的描述信息的属性和方法
- 线程Id : 在JVM进程中唯一,由JVM进行管理
属性:private long tid,
方法:public long getId(),获取线程 Id;线
- 线程名称
属性:private String name,该属性保存一条 Thread 线程实例的名字。
方法一:public final String getName(),获取线程名称。
方法二:public final void setName(String name),设置线程名称。
方法三:Thread(String threadName),通过此构造方法,给线程设置一个定制化的名字。
- 线程优先级
- 是否为守护线程
private boolean daemon = false,该属性保存 Thread 线程实例的守护状态,默认为 false,
表示是普通的用户线程,而不是守护线程。
方法:public final void setDaemon(boolean on),将线程实例标记为守护线程或用户线程,如果
参数值为 true,则将线程实例标记为守护线程。
- 线程的状态
- 线程的启动和运行
方法一:public void start(),用来启动一个线程,当调用start方法后,JVM才会开启一个线程来执行用户定义的线程代码逻辑,这个过程,会为新的线程分配需要的资源
方法二:public void run(),作为线程代码逻辑的入口方法。run方法不是由用户来调用,当调用start方法启动一个线程后,只要线程获得了CPU执行时间,便进入了run方法体去执行具体的用户线程代码
- 获取当前线程
public static Thread currentThread(),该方法是一个非常重要的静态方法,获取当前线程的 Thread 实例对象。
在没有其他的途径获取当前线程的实例对象的时候,可以通过 Thread.currentThread()静态方法获取。
3.1.2通过继承Thread类创建线程
需要执行的步骤:
(1)需要继承Thread类,创建一个新的线程类
(2)同时重写run()方法,将需要并发执行的业务代码编写在run方法中
static class DemoThread extends Thread{
public DemoThread(){
super("DemoThread"+threadNo++);
}
@Override
public void run() {
for (int i = 1; i <=5; i++) {
System.out.println(getName()+"轮次:"+i);
}
System.out.println("运行结束");
}
public static void main(String[] args) {
Thread thread=null;
//通过Thread子类创建和启动 2个线程
for (int i = 0; i < 2; i++) {
thread=new DemoThread();
thread.start();
}
System.out.println("主线程结束");
}
}
3.2 实现Runnable接口创建线程目标类
3.2.1关于Thread类和Runnable接口
public class Thread implements Runnable {
private Runnable target; //执行目标
public void run() {
if(this.target != null) {
this.target.run(); //调用执行目标的 run()方法
}
}
public Thread(Runnable target) { //包含执行目标的构造器
init(null, target, "Thread-" + nextThreadNum(), 0);
}
}
在Thread类中的run()方法中,如果target(执行目标)不为空,就执行target属性的run方法而 target 属性是 Thread 类的一个实例属性,并且 target 属性的类型为 Runnable。
Thread类的构造器
//可以为target赋值的
(1)public Thread(Runnable target)
(2)public Thread(Runnable target,String name)
(3)public Thread(ThreadGroup group, Runnable target, String name)
关于Runnable接口
@FunctionalInterface //函数是接口 只有一个方法的接口
public interface Runnable {
void run();
}
3.2.2通过实现Runnable接口创建线程类
创建步骤:
(1)定义一个新类实现Runnable接口
(2)实现Runnable接口中的run()抽象方法,
(3)通过Thread类创建线程对象:将Runnable实现类实例,作为实参传递给Thread的构造器。将该Runnable实例作为Thread的目标属性target
(4)调用Thread实例的start()方法
public class CreateDemo1 {
static int threadNo=1;
static class RunTarget implements Runnable{
@Override
public void run() {
for (int i = 1; i <=5; i++) {
System.out.println(Thread.currentThread().getName()+"轮次:"+i);
}
System.out.println("运行结束");
}
}
public static void