Java多线程
了解多线程之前,需要了解进程与线程之间的关系
进程与线程
进程: 是程序的一次动态执行过程,它对应了从代码加载,执行至执行完毕的一个完整过程。
进程是操作系统分配的最小执行单位,是一个独立的执行实例,拥有自己的内存空间、系统资源和进程控制块等信息。每个进程都是独立的,相互之间不会影响,可以并发执行。进程之间通常通过进程间通信(IPC)来进行数据交换和同步操作。
线程是进程中的一个执行单元,是比进程更小的执行单位。线程共享进程的内存空间和系统资源,但每个线程拥有自己的栈和寄存器等信息。多个线程可以在同一进程中并发执行,可以共享数据,但需要注意线程之间的同步和互斥问题。
相对于进程,线程的创建、销毁和切换等开销更小,因此线程可以更快地响应用户的操作,提高系统的并发度和响应能力。但是,由于线程共享进程的资源,线程之间的同步和互斥问题比较复杂,需要开发者进行仔细的设计和实现。
Java中的线程
Java的多线程机制
Java语言的一大特性点就是内置对多线程的支持。
多线程是指一个应用程序中同时存在几个执行体,按几条不同的执行线索共同工作的情况,它使得编程人员可以很方便地开发出具有多线程功能、能同时处理多个任务的功能强大的应用程序。
虽然执行线程给人一种几个事件同时发生的感觉, 但这只是一种错觉, 因为我们的计算机在在何给定的时刻只能执行那些线程中的一个。
为了建立这些线程正在同步执行的感觉,Java虚拟机快速地把控制从一个线程切换到另一个线程。 这些线程将被轮流执行,使得每个线程都有机会使用CPU资源。
主线程(main线程)
每个Java应用程序都有一个缺省的主线程。
我们已经知道,Java 应用程序总是从主类的main方法开始执行。
当JVM加载代码,发现main方法之后,就会启动一个线程,这个线程称为“主线程”(main 线程),该线程负责执行main方法。
那么,在main方法的执行中再创建的线程,就称为程序中的其他线程。如果main方法中没有创建其他的线程,那么当main方法执行完最后一个 语句,即main 方法返回时,JVM就会结束我们的Java应用程序。
如果main方法中又创建了其他的线程,那么JVM就要在主线程和其它线程之间轮流切换,保证每个线程都有机会使用CPU资源,main方法即使执行完最后的语句,JVM也不会结束Java应用程序,JVM要一直等到Java应用程序中的所有线程都结束之后,才结束Java应用程序。
public class Main {
public static void main(String[] args) throws InterruptedException {
MyThread thread = new MyThread();
thread.start();
System.out.println("主线程结束了哦");
}
}
class MyThread extends Thread{
@Override
public void run(){
try {
System.out.println("我开始运行了哦");
Thread.sleep(5000);
System.out.println("我结束运行了哦");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
大家可以跑一下上面的代码,你会发现主线程结束后,你的编译器仍然在运行,而且5s过后才结束。
操作系统让各个进程轮流执行,当轮到Java应用程序执行时,Java虚拟机就保证让Java应用程序中的多个线程都有机会使用CPU资源,即让多个线程轮流执行。
如果机器有多个CPU,那么JVM就能充分利用这些CPU,获得真实的线程并发执行效果。
线程的状态与生命周期
Java语言使用Thread类及其子类的独享来表示线程,新建的线程在它的一个完整的生命周期中通常要经历如下的4种状态:
-
新建
当一个Thread类及其子类的对象被声明并创建时,新生的线程对象处于新建供态。此时线程创建之后就具备了运行的条件,且轮到它来享用CPU资源时,即JVM将CPU使用权切换给该线程时,此线程就可以脱离创建它的主线程独立开始自己的生命周期了。
-
运行
线程创建后仅仅是占有了内存资源,在JVM管理的线程中还没有这个线程,此线程必须调用start方法(从父类继承的方法)通知JVM,这样JVM就会知道又有一个新线程排队等候切换了。
当JVM将CPU使用权切换给线程时,如果线程是Thread的子类创建的,该类中的run()方法就立刻执行,run方法规定了该线程的具体使命。所以程序必须在子类中重写父类的run方法,其原因是Thread类中的run方法没有具体内容,程序要在Thread类的子类中重写run方法来覆盖父类的run方法。在线程没有结束run方法之前,不要让线程再调用start方法,否则将发生IllegalThreadStateException异常。
-
中断
有4种原因的中断:
- JVM将CPU资源从当前线程切换给其他线程.使本线程让出CPU的使用权处于中断状态。(JVM是一个进程,由它来分配线程)
- 线程使用CPU 资源期间,执行了sleep(int millsecond)方法,使当前线程进入休眠状态。sleep方法是 Thread类的一个类方法, 线程一旦执行了sleep方法,线程一旦执行了sleep方法,就立刻让出CPU的使用权,使当前线程处于中断状态。经过参数(int millsecond)指定的毫秒数之后,该线程就重新进到线程队列中排队等待CPU资源(是排队,不是直接去执行),以便从中断处继续运行。
- 线程使用CPU资源期间,执行了wait()方法,使得当前线程进入等待状态。等待状态的线程不会主动进到线程队列中排队等待CPU资源,必须由其它线程调用notify()方法通知它,使得它重新进到线程队列中排队等待CPU资源,以便从中断处继续运行。
- 线程使用CPU资源期间,执行某个操作进入阻塞状态,比如执行读/写操作引起阻塞。进入阻塞状态时线程不能进入排队队列,只有当引起阻塞的原因消除时,线程才重新进到线程队列中排队等待CPU资源,以便从原来中断处开始继续运行。
-
死亡 处于死亡状态的线程不具有继续运行的能力。线程死亡的原因有二:
线程完成了它的全部工作,即执行完run方法中的全部语句,结束了run方法。
另一个原因:线程被提前强制性地终止,即强制run方法结束。
所谓死亡状态就是线程释放了实体,即释放分配给线程对象的内存。
线程调度与优先级
处于就绪状态的线程首先讲入就绪队列排队等候 CPU 资源, 同一时刻在就绪队列中的线程可能有多个。
Java虚拟机中的线程调度器负责管理线程,调度器把线程的优先级分为10个级别,分别用 Thread类中的类常量表示。
每个Java 线程的优先级都在常数1和10之间,即 Thread.MIN_PRIORITY
和 Thread.MAX_PRIORITY
之间。
如果没有明确地设置线程的优先级别,每个线程的优先级都为常数5,即 Thread.NORM_PRIORITY
。
线程的优先级可以通过setPriority(int grade)方法调整,该方法需要一个int类型参数。
如果参数不在1~10的范围内,那么setPriority 便产生一个 IllegalArgumenException
异常。
getPriority方法返回线程的优先级。
需要注意是,有些操作系统只识别3个级别:1、5和10。
通过前面的学习已经知道,在采用时间片的系统中,每个线程都有机会获得 CPU的使用权,以便使用 CPU 资源执行线程中的操作。
当线程使用CPU资源的时间到时后,即使线程没有完成自己的全部操作,JVM也会中断当前线程的执行,把CPU 的使用权切换给下一个排队等待的线程,当前线程将等待 CPU 资源的下一次轮回,然后从中断处继续执行。
JVM的线程调度器的任务是使高优先级的线程能始终运行,一旦时间片有空闲,则使具有同等优先级的线程以轮流的方式顺序使用时间片。
也就是说,如果有A、B、C、D四个线程, A和B的级别高于C和D,那么,Java调度器首先以轮流的方式执行A和B,一直 等到A,B都执行完毕进入死亡状态,才会在C,D之间轮流切换。
在实际编程时, 不提倡使用线程的优先级来保证算法的政务额执行。要编写正确,跨平台等待多线程代码,必须假设线程在任何时刻都有可能被剥夺CPU资源的使用群