1.线程的简介
1.1.什么是进程
进程是操作系统进行资源分配和调度的基本单位。它可以被看作是程序的一次执行过程,拥有自己独立的内存空间,包括代码段、数据段、堆、栈等。进程间的资源是相互隔离的,一个进程的错误通常不会影响到其他进程。
例如,打开一个浏览器就是启动了一个进程,打开一个文本编辑器又是另一个进程。这些进程各自独立运行,拥有自己的资源。
1.2.什么是线程
线程是进程中的一个执行单元,是进程内的一条执行路径。线程共享所属进程的资源,如内存空间、文件描述符等,但每个线程有自己的程序计数器、栈和寄存器等少量私有数据。线程的创建和切换开销比进程小,因此在需要大量并发执行任务时,使用线程可以提高系统的性能和响应速度。
例如,在一个浏览器进程中,可能有多个线程同时工作,一个线程用于处理用户的输入,一个线程用于加载网页内容,一个线程用于更新页面显示等。
1.3.进程和线程的区别
1.进程是操作系统运行的一个任务,线程是进程中运行的一个任务
2.进程是资源分配的最小单位(相互独立),线程是程序执行的最小单位(cpu调度的基本单元)。
3.进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。
4.一个进程结束,其内的所有线程都结束,但不会对另外一个进程造成影响。多线程程序,一个线程结束,有可能会造成其他线程结束
1.4.CPU
现在,一个CPU都是多核心的。
内核(处理器):一个CPU内核在一个时间片上可以执行一个线程
逻辑处理器:同时可以处理的线程数量。(超线程时使用)
1.5. 线程调度
线程调度是计算机多线程操作系统中分配CPU时间给各个线程的过程。每个线程代表程序中的一个执行路径,操作系统通过线程调度器分配处理器时间,决定哪个线程将获得执行的机会,以及获得的时间长短。
1.6. 进程调度
进程调度是操作系统中分配CPU时间给各个进程的过程。进程是系统进行资源分配和调度的独立单位,它包含代码、数据以及分配的系统资源。与线程调度不同,进程调度涉及到的上下文切换成本更高,因为进程间共享的资源更少。
进程调度的算法之一:时间片轮转算法(Round-Robin,RR)
CPU 时间片轮转机制是一种抢占式调度算法,即 CPU 会分配给每个进程一个固定时间片,当一个进程的时间片用尽后,系统会打断该进程并分配给下一个进程。这一过程会一直进行下去,直到所有进程都被执行完毕。
实现原理:
-
系统将所有的就绪进程按先来先服务的原则,排成一个队列,
-
每次调度时,把CPU分配给队首进程,并令其执行一个时间片.时间片的大小从几ms到几百ms.
-
当执行的时间片用完时,由一个计时器发出时钟中断请求,调度程序便据此信号来停止该进程的执行,并将它送往就绪队列的末尾;
-
然后,再把处理器分配给就绪队列中新的队首进程,同时也让它执行一个时间片
Windows 系统中线程轮转时间也就是时间片大约是20ms,如果某个线程所需要的时间小于20ms,那么不到20ms就会切换到其他线程;如果一个线程所需的时间超过20ms,系统也最多只给20ms,除非意外发生(那可能导致整个系统无响应),而Linux/unix中则是5~800ms。
1.7. 串行与并发
多个线程“同时运行”只是我们感官上的一种表现。其实,线程是并发运行的。
操作系统将时间划分成很多时间片段,尽可能的均匀分配给每一个线程,获取时间片段的线程被CPU运行,而其他线程处于等待状态。所以这种微观上是走走停停,断断续续的,宏观上都在运行的现象叫并发。但不是绝对意义上的“同时发生”。
2.线程的调度机制
2.1 java线程的状态简介
在Java中,线程(Thread)可以处于以下几种状态:
新建状态(New):线程对象已经创建,但还没有调用start()方法。
就绪状态(Runnable):线程已经调用start()方法,等待CPU调度执行。
运行状态(Running):线程获得CPU时间片,开始执行run()方法里的代码。
阻塞状态(Blocked):线程因为某些原因放弃CPU使用权,暂时停止运行,直到进入就绪状态。
等待状态(Waiting):线程因为某些条件而进入等待状态,此时不会被分配CPU时间片,直到其他线程显式地唤醒。
超时等待状态(Timed Waiting):线程在指定的时间内等待,时间到后会自动返回到就绪状态。
终止状态(Terminated):线程的run()方法执行完毕或者因异常退出而结束线程的生命周期。
2.2 抢占式调度与协同式调度
JVM线程调度的实现依赖于底层操作系统的支持。由于Java是跨平台的,JVM会利用底层操作系统提供的功能来管理线程的调度。
Java线程调度基本上是抢占式的,在这种模式下,每个线程都有机会获得CPU时间片,操作系统基于线程的优先级来决定哪个线程更应该运行。高优先级的线程会得到更多的运行机会。
而相对的协同式调度则要求线程主动释放控制权,当前运行的线程必须主动让出CPU时间,其他线程才能获得执行机会。但在Java中,由于大多数现代操作系统都采用抢占式调度,协同式调度在Java中并不常见。
2.3 线程的优先级
线程的切换是由线程调度控制的,我们无法通过代码来干涉,但是我们可以通过提高线程的优先级来最大程度的改善线程获取时间片段的概率。Java线程可以通过setPriority方法设置优先级,优先级较高的线程有更大的几率获得CPU时间片。
线程的优先级被划分为10级,值分别为1-10,其中1最低,10最高。线程提供了三个常量来表示最低,最高,以及默认优先级
-
Thread.MIN_PRIORITY
-
Thread.MAX_PRIORITY
-
Thread.NORM_PRIORITY
2.4 线程生命周期管理
JVM通过Thread类提供的方法来管理线程生命周期,例如start()、sleep()、yield()、join()、wait()等,使得线程在适当的时候运行或暂停
-
start()方法可以使线程处于就绪状态
-
yield()方法可以使当前运行的线程让出自己的时间片,但不会阻塞线程,只是将线程从运行状态转移至就绪状态。
-
join()方法可以让一个线程等待另一个线程完成后再继续执行。
-
sleep()方法可以使当前线程暂停执行指定时间。
-
wait()方法是当前线程释放锁,释放cpu等资源,进入等待状态
3.线程的创建及其常用API
3.1线程的三种创建方式
3.1.1第一种
继承Thread类,重写run方法,创建该类对象,调用start方法开启线程。start方法可以将该线程对象纳入可执行线程池中。
public class _01CreatDemo {public static void main(String[] args) {//创建一个线程对象,使其处于新建状态Thread m1 = new MyThread();//启动线程,使其处于就绪状态m1.start();//使用匿名内部类的方式来实现Thread创建一个线程对象Thread t1 = new Thread(){public void run(){for(int i=0; i<100; i++){System.out.println("Hello World");}}};t1.start();} } class MyThread extends Thread{//run方法就是用来编写线程的任务代码public void run(){for(int i=0;i<100;i++){System.out.println(Thread.currentThread().getName()+" "+i);}} }
3.1.2第二种
实现Runnable接口,重写run方法,创建Thread类对象,将Runnable子类对象传递给Thread类对象。调用start方法开启线程。
public class _02CreatDemo {public static void main(String[] args) {//获取Runnable的实例对象Runnable task = new MyTask();//创建线程对象,调用构造器Thread(Runnable runnable).传入任务Thread thread = new Thread(task);//启动线程thread.start();//使用匿名内部类或者Lambda表达式,创建线程对象Thread t2=new Thread(() ->{int sum=0;for (int i = 0; i < 100; i+=2) {sum+=i;}System.out.println("sum="+sum);});t2.start();} } class MyTask implements Runnable{@Overridepublic void run() {int sum=0;for (int i = 0; i < 100; i++) {if (i % 2 == 0) {sum+=i;}}System.out.println(sum);} }
3.1.3第三种
public class _03CreatDemo {public static void main(String[] args) throws ExecutionException, InterruptedException {//Callable是函数式接口,里面的V call()相当于Thread或者Callable c1=()->{int sum=0;for (int i=2;i<=100;i+=2) {sum+=i;}return sum;};//调用FutureTask的构造器,传入Callable对象FutureTask<Integer> f1=new FutureTask<>(c1);//创建Thread线程对象,调用start方法new Thread(f1).start();//获取线程执行结束后的结果,注意:get()有阻塞所在线程的效果int result =f1.get();System.out.println(result);} }
3.2常用的构造器
Thread():
这种构造器创建一个新的线程对象,但没有指定要执行的任务。在创建后,需要通过Thread对象的start方法启动线程,并在启动前通过Thread对象的run方法设置线程要执行的具体逻辑。
Thread(Runnable target):
这个构造器接收一个实现了Runnable接口的对象作为参数,指定了线程要执行的任务。当通过start方法启动线程时,会执行Runnable对象中的run方法定义的逻辑。
Thread(Runnable target, String name):
除了接收要执行的Runnable任务对象外,还接收一个表示线程名称的字符串参数。这样可以为线程指定一个有意义的名称,方便在调试和监控时进行区分和识别。
3.3常用的属性方法
方法名 | 用途 |
---|---|
static Thread currentThread() | Thread类的静态方法,可以用于获取运行当前代码片段的线程对象 |
long getId() | 返回该线程的标识符 |
String getName() | 返回该线程的名称 |
int getPriority() | 返回该线程的优先级 |
Thread.State getState() | 获取线程的状态 |
boolean isAlive() | 判断线程是否处于活动状态 |
boolean isInterrupted() | 判断线程是否已经中断 |
boolean isDaemon() | 判断线程是否为守护线程 |
3.4守护进程的说明
守护线程与普通线程在表现上没有什么区别,我们只需要通过Thread提供的方法来设定即可:
-
void setDaemon(boolean on)
当参数为true时该线程为守护线程
3.5声明周期的相关方法
3.5.1 sleep()方法
-
static void sleep(long millis)
线程睡眠方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()方法平台移植性好。
3.5.2 yield()方法
-
static void yield()
线程让步方法,暂停当前正在执行的线程对象,使之处于可运行状态,把执行机会让给相同或者更高优先级的线程。
3.5.3 join()方法
-
void join()
线程加入方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态