7月3日----线程的基本概念、线程类、任务类、线程优先级、sleep()方法(休眠)、yield()方法(礼让)、join方法(合并)、interrupt()方法(中断)
标签: 7月3日----线程的基本概念、线程类、任务类、线程优先级、sleep()方法(休眠)、yield()方法(礼让)、join方法(合并)、interrupt()方法(中断) Java博客 51CTO博客
2023-07-08 18:24:20 69浏览
一、什么是进程
进程是系统进行资源分配和调用的独立单元,每一个进程都有它的独立内存空间和系统资源。
二、单进程操作系统和多进程操作系统的区别
单进程操作系统:dos(一瞬间只能执行一个任务)
多进程单用户操作系统:Windows(一瞬间只能执行多个任务)
多进程多用户操作系统:Linux(一瞬间只能执行多个任务)
三、现在的多核CPU是否可以让系统在同一个时刻可以执行多个任务吗?
理论上是可以的
四、什么是线程,理解线程和进程的关系
什么是线程?
线程是进程里面的一条执行路径,每个线程同享进程里面的内存空间和系统资源
一个进程 可以有 多个线程:各个线程都有不同的分工
理解线程和进程的关系
进程 与 进程 之间的关系:进程之间的内存空间和系统资源是独立的
同一个进程里的多条线程 :线程之间的内存空间和系统资源是共享的
进程里:可以有一条或一条以上的线程
进程里只有一条线程的情况下,这条线程就叫做主线程
进程里有多条线程的情况下,只有一条线程叫做主线程
Ps:线程是在进程里的,他们是包含关系
五、我们应用的软件有哪些是多线程的应用?
都是
六、Java中,如何来编写多线程的应用程序?有哪些方法?
1.线程类
创建MyThread类,继承Thread,重写run方法
测试类:
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
线程类:
public class MyThread extends Thread{
@Override
public void run() {
System.out.println("run方法别调用了");
}
}
1.**创建线程对象:**MyThread myThread = new MyThread();
2.**启动线程:**myThread.start();
输出结果:
run方法别调用了
2.任务类
创建Task类,实现Runnable接口中的run方法
测试类
public static void main(String[] args) {
//创建任务对象
Task task = new Task();
//创建线程对象
Thread t = new Thread(task);
//启动线程
t.start();
}
任务类 1.重写Task**(任务类),实现**Runnable
2.重写run方法
public class Task implements Runnable{
@Override
public void run() {
System.out.println("run方法被调用了");
}
}
输出结果
run方法别调用了
案例1
编写一个多线程的应用程序,主线程打印1-100之间的数字,子线程打印200-300之间的数字, 观察其输出的结果,体会多线程互相争抢资源的场景
测试类
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
for (int i = 1;i<=100;i++){
System.out.println(i);
}
}
线程类
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 200;i <= 300;i++){
System.out.println(i);
}
}
}
输出结果
谁先抢到资源,谁就先打印
总结
进程 与 进程 的关系:独享内存空间和系统资源
**线程 与 进程 的关系:**有一个进程中至少包含一个线程
**线程 与 线程 的关系:**在同一个进程里,多个线程共享内存空间和系统资源
一个进程中包含多个线程,只有一个主线程
经典面试题:请问当我们编写一个单纯的main方法时,此时该程序是否为单线程的?为什么?
垃圾回收器是一个后台线程
可以设置线程的优先级:
代码演示
测试类
public static void main(String[] args) {
A a = new A();
B b = new B();
C c = new C();
a.setPriority(Thread.MAX_PRIORITY);//10 //最大优先级
b.setPriority(Thread.NORM_PRIORITY);//5 //默认的优先级
c.setPriority(Thread.MIN_PRIORITY);//1
a.start();
b.start();
c.start();
}
}
A,B,C线程类
public class A extends Thread{
@Override
public void run() {
for(int i = 1;i<=100;i++){
System.out.println("A:" + i);
}
}
}
public class B extends Thread{
@Override
public void run() {
for(int i = 1;i<=100;i++){
System.out.println("B:" + i);
}
}
}
public class C extends Thread{
@Override
public void run() {
for(int i = 1;i<=100;i++){
System.out.println("C:" + i);
}
}
}
总结
虽然给三个线程设置了优先级,只是A线程比BC先抢到现成的可能性大,而不是绝对的是A线程一定最先抢到。
输出结果:
这张图可以看出B,C都比A先打印。设置优先级,只是提高了他抢到资源的可能性。将资源倾斜给优先级高的。
也可以加入类与对象的概念
public class MyThread extends Thread{
private String myThread;
public MyThread(String myThread) {
this.myThread = myThread;
}
@Override
public void run() {
for(int i = 1;i<=100;i++){
System.out.println(myThread +"---"+ i);
}
}
}
测试
public static void main(String[] args) {
MyThread a = new MyThread("A");
MyThread b = new MyThread("B");
MyThread c = new MyThread("C");
a.setPriority(Thread.MAX_PRIORITY);//10 //最大优先级
b.setPriority(Thread.NORM_PRIORITY);//5 默认的优先级
c.setPriority(Thread.MIN_PRIORITY);//1
a.start();
b.start();
c.start();
}
}
引入对象与类后,就不需要创建A,B,C三个类,这样更加节省空间和资源。
给线程定义名称的好处
- 方便调试和日志记录:给线程定义一个有意义的名称可以方便追踪和调试程序。在日志记录中,可以通过线程名称区分不同线程的日志输出,便于排查问题。
- 提高代码可读性:给线程定义一个描述性的名称可以让代码更易读和理解。通过名称可以清楚地表达线程的作用和功能,使代码更具可维护性。
- 便于统计和监控:在多线程应用中,可以通过线程名称对线程进行统计和监控。例如,可以统计某个特定类型的线程数量,或者通过名称过滤出特定线程进行监控。
- 区分线程的作用和优先级:通过线程名称可以清晰地区分不同线程的作用和优先级。这对于多线程应用中的任务调度和资源管理非常重要。
1、
public class MyThread extends Thread{
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for(int i = 1;i<=100;i++){
//获取当前的线程对象
Thread t = Thread.currentThread();
//获取当前线程对象名
String str = t.getName();
System.out.println(str +"---"+ i);
}
}
}
2、给线程定义名称
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "中的run方法被调用了");
}
}, "线程1");//线程1就是定义的name名字
t.start();
}
利用匿名内部类
线程的休眠
线程的休眠是指让线程暂时停止执行一段时间,然后再继续执行。在休眠期间,线程不会占用CPU资源,可以用来等待某个条件满足或者定时执行任务。
线程的休眠可以通过调用线程的sleep()方法来实现。sleep()方法接受一个参数,表示线程休眠的时间,单位是毫秒。线程在休眠期间会进入阻塞状态,直到休眠时间结束后才会被唤醒继续执行。
线程的休眠可以用于实现一些需要定时执行的任务,也可以用于控制线程的并发执行。但需要注意的是,线程的休眠时间不是绝对准确的,受到系统调度和其他因素的影响,实际休眠时间可能会有一定的误差。
此方法为静态方法,写在哪个线程中,哪个线程就休眠
案例:编写一个提取成分的程序,要求在点中名字三秒后输出被抽中的名字
public static void main(String[] args) throws InterruptedException {
String[] names = {"水","盐","油","五香粉","孜然","茴香"};
Random ran = new Random();//随机抽取
int index = ran.nextInt(names.length);
for (int i = 3; i > 0; i--) {
System.out.println(i);
Thread.sleep(1000);//直接抛异常
//休眠1000毫秒
}
System.out.println("被抽中的成分为:" + names[index]);
}
线程的礼让
线程的礼让是指一个线程主动让出CPU资源,使其他线程有机会执行。在Java中,可以使用Thread类的yield()方法实现线程的礼让。
理解:让当前线程退出CPU资源,并马上进入抢资源的状态
**yield()方法是一个静态方法,**调用它的线程会将自己的执行权暂时让给其他具有相同或更高优先级的线程。如果没有其他具有相同或更高优先级的线程,那么yield()方法将不会产生任何效果。
当一个线程调用yield()方法后,它会进入就绪状态,等待系统重新调度。然后,系统会从就绪状态的线程中选择一个线程执行,这个选择是由系统的线程调度算法决定的。
需要注意的是,yield()方法不能保证当前线程一定会被暂停执行,它只是提供了一种提示,告诉系统当前线程愿意让出CPU资源。具体是否暂停执行由系统的线程调度算法决定。
线程的礼让可以用于合理调度线程的执行顺序,提高多线程程序的性能和效率。但需要注意的是,过多地使用yield()方法可能会导致线程的频繁切换,降低程序的性能。因此,在使用yield()方法时需要谨慎评估和控制。
案例:创建两个线程A,B,分别各打印1-100的数字, 其中B一个线程,每打印一次,就礼让一次,观察实验结果
public static void main(String[] args) {
A a = new A();
B b = new B();
a.start();
b.start();
}
public class A extends Thread{
@Override
public void run() {
for (int i = 1; i <=100 ; i++) {
System.out.println("A"+"---" + i);
}
}
}
public class B extends Thread{
@Override
public void run() {
for (int i = 1; i <=100 ; i++) {
System.out.println("B"+"---" + i);
//礼让
Thread.yield();
}
}
}
输出结果
线程的合并
线程的合并是指一个线程等待另一个线程执行完毕后再继续执行。在Java中,可以使用Thread类的join()方法实现线程的合并。
**join()方法是一个实例方法,**调用它的线程会等待被调用的线程执行完毕。如果被调用的线程已经执行完毕,那么join()方法会立即返回。
当一个线程调用join()方法后,它会进入阻塞状态,等待被调用的线程执行完毕。在被调用的线程执行完毕后,调用join()方法的线程会解除阻塞状态,继续执行。
需要注意的是,join()方法可以指定一个超时时间,如果超过指定的时间被调用的线程仍未执行完毕,那么join()方法会返回。
线程的合并可以用于控制线程的执行顺序,确保某个线程在其他线程执行完毕后再执行。这对于多线程协作和结果的处理非常有用。
案例:主线程和子线程各打印200次,从1开始每次增加1,当主线程打印到10之后,让子线程先打印完再打印主线程
子线程类MyThread
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 1; i <=100 ; i++) {
System.out.println("子线程:" + i);
}
}
}
主线程
public static void main(String[] args) throws InterruptedException {
MyThread t = new MyThread();
t.start();
for (int i = 1; i <= 200; i++) {
System.out.println("主线程:" + i);
if(i == 10){
t.join();//底层是要抛异常的
}
}
}
理解图:
运行结果
在主线程中,i<= 10之前,主线程和子线程会争抢资源,在i大于10后,在主线程中加入了合并方法,t.join(),接下来这就会让子线程把i循环结束,才会循环主线程中i>10 的部分。
也存在一种情况,在主线程i<=10之前,子线程就把i<=100打印结束了。就不会存在合并。
线程的中断
线程的中断是指在一个线程中断另一个线程的执行。在Java中,可以使用Thread类的interrupt()方法实现线程的中断。(run方法执行完毕)
interrupt()方法是一个实例方法,调用它的线程会向被调用的线程发送中断信号。被调用的线程可以通过检查自身的中断状态来响应中断信号,通常会在合适的时机中断自己的执行。
**被调用的线程可以通过调用Thread类的静态方法interrupted()来检查自身的中断状态。**如果线程的中断状态为true,表示该线程被中断了。
当一个线程被中断时,它可以选择继续执行或者中断自己的执行。通常情况下,被中断的线程会在合适的时机检查自身的中断状态,并根据中断状态来决定是否继续执行。
需要注意的是,调用interrupt()方法并不会立即中断线程的执行,它只是发送一个中断信号。被中断的线程需要在合适的时机检查中断状态,并主动中断自己的执行。
需要注意的是,线程的中断只是一种通知机制,被中断的线程需要根据中断状态来决定是否中断自己的执行。同时,线程在执行过程中可以通过捕获InterruptedException异常来处理中断信号。
代码示例
@Override
public void run() {
while(!Thread.currentThread().isInterrupted()){
//判断当前线程是否消亡(true - 消亡,flase - 未消亡 )
System.out.println(Thread.currentThread().isInterrupted());
System.out.println(111);
System.out.println(222);
System.out.println(333);
System.out.println(444);
}
/**
* 当线程被中断后,Thread.currentThread().isInterrupted()会返回true,循环会 跳出。
*/
}
测试类:
public static void main(String[] args) throws InterruptedException {
MyThread t = new MyThread();
t.start();
Thread.sleep(5000);//5秒钟后去关闭
t.interrupt();//中断这个方法
//理解:改变线程的状态 -- 是否中断的状态(开始为flase(未消亡),调用该方法后true(消亡))
}
注意:当用stop()方法去中断线程时--线程立即终止 -- 不可取,因为无法保证功能的完整性
守护线程/后台线程
守护线程(Daemon Thread)是一种特殊类型的线程,在Java中通过设置线程的setDaemon(true)方法将线程设置为守护线程。
守护线程的特点是:当所有非守护线程结束时,守护线程会自动退出,不管守护线程是否执行完毕。
守护线程通常用于执行一些后台任务,如垃圾回收、日志记录等。它们不会阻止JVM的退出,因此在应用程序中使用守护线程时需要注意它们的生命周期和执行逻辑.
示例代码实现:
public class MyThread extends Thread{
//父类的run方法没有抛出异常,子类重写的run方法也不能抛出异常
//只能使用try{}catch{}
@Override
public void run() {
while(true){
System.out.println("后台线程守护着前台线程");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
测试
public static void main(String[] args) throws InterruptedException {
MyThread t = new MyThread();
t.setDaemon(true);//将线程对象(t)设置为后台线程
t.start();
for (int i = 1; i <=5 ; i++) {
System.out.println("前台线程:" + i);
Thread.sleep(1000);
}
}
输出结果:
理解:在打印五个前台线程后,后台线程守护完最后一个前台线程,在没发现有除本身线程外的线程时,直接退出。
线程的生命周期
1、新建状态
i. 在程序中用构造方法创建了一个线程对象后,新的线程对象便处于新建状态,此时,它已经有了相应的内存空间和其它资源,但还处于不可运行状态。新建一个线程对象可采用线程构造方法来实现。
ii. 例如:Thread thread=new Thread();
2、 就绪状态
i. 新建线程对象后,调用该线程的start()方法就可以启动线程。当线程启动时,线程进入就绪状态。此时,线程将进入线程队列排队,等待CPU调用,这表明它已经具备了运行条件。
3、运行状态
i. 当就绪状态的线程被调用并获得处理器资源时,线程就进入了运行状态。此时,自动调用该线程对象的run()方法。run()方法定义了该线程的操作和功能。
4、 阻塞状态
i. 一个正在执行的线程在某些特殊情况下,如被人为挂起,将让出CPU并暂时中止自己的执行,进入阻塞状态。在可执行状态下,如果调用sleep(2000)、wait()等方法,线程都将进入阻塞状态。阻塞时,线程不能进入排队队列,只有当引起阻塞的原因被消除后,线程才可以转入就绪状态。
5、死亡状态
i. 线程调用stop()方法时或run()方法执行结束后,线程即处于死亡状态。处于死亡状态的线程不具有继续运行的能力。
线程的生命周期理解图:
好博客就要一起分享哦!分享海报
此处可发布评论
评论(0)展开评论
展开评论
您可能感兴趣的博客