【JavaEE】初识线程


一、简述进程

认识线程之前我们应该去学习一下“进程" 的概念,我们可以把一个运行起来的程序称之为进程,进程的调度,进程的管理是由我们的操作系统来管理的,创建一个进程,操作系统会为每一个进程创建一个 PCB,并为进程在内存中开辟一块运行空间 ,然后把这个 PCB 加入到链表中。

进程的调度是为了解决,处理多进程运行的机制,CPU 按照并发的方式执行进程,在进程之间高速切换,看起来就是多进程同时运行。为了描述控制进程的运行,系统中存放进程的管理和控制信息的数据结构称为进程控制块(PCB Process Control Block),它是进程实体的一部分,是操作系统中最重要的记录性数据结构。它是进程管理和控制的最重要的数据结构,每一个进程均有一个PCB,PCB 记载了进程的优先级,进程的状态,进程的上下文,进程的记账信息等等。

进程的概念就是为了能够实现,多任务,并发执行(”同时执行“)的机制。

能否实现多进程并发执行,需要看操作系统是否能够支持操作,执行的效率这个是考验CPU 的性能。

想要学习更多进程知识的朋友可以观看博主的上一篇博客——【JavaEE】浅识进程_保护小周ღ的博客


二、什么是线程

进程是操作系统分配资源的基本单位,无论是创建,还是调度都是非常麻烦的,进程与进程之间的独立性是较高的,多进程协同维护同一个程序,对资源的消耗是非常大的。

举个例子:我们的QQ 当我们与别人打qq电话的时候,还可以接收qq 消息,并且qq空间同一时刻也接收了你铁哥们发的动态,做个假设,这三个功能是不是可以看作是三个进程,他们可以相互独立的运行,不受其他进程的影响,进程之间又有着某种关联,可以相互通信,他们共同维护了 qq 这个应用。

那么这个假设的例子:我们运行qq , 操作系统此时要给三个进程分配系统资源,开辟内存空间,而且还要花费精力保证进程之间的通信、关联性,是不是很复杂~此时的复杂主要体现在系统资源的分配上。


2.1 线程的概念

线程是建立在进程的基础之上的,可以看作是轻量级的进程,一个进程可以包含一个或者多个线程,同一个进程下的线程之间都是独立且可以调度执行的“执行流”,也是并发执行的,这些线程之间共用父进程的系统资源。

举个例子:


根据以上实例:

  1. 线程是建立在进程的基础上的,进程包含线程

  1. 同一个进程内部,多个线程之间,共用一份系统提供给进程的资源(内存空间,资源共享)

  1. 启动进程后(一般是会创建一个线程,例如 JAVA ,C/C++ 中的main() 函数,程序从main()函数开始执行,操作系统为main() 函数开辟栈帧,然后CPU 的寄存器处理、维护栈帧),需要系统花时间,花精力去分配系统资源,进程创建完毕后,无论当中有多少个线程,站在进程的角度上都不需要再去申请系统资源了,线程之间共用一份进程资源。

  1. 进程是系统分配资源的基本单位。

  1. 操作系统真正调度的是线程,线程是操作系统调度运行的基本单位。

  1. 进程之间相互独立,同一个进程之下,线程之间共享进程资源,此时如果其中一个线程崩溃有可能会对其他线程造成影响,甚至是崩溃。

  1. 一个进程中的线程数应当设计合理,线程之间也是并发执行,而CPU 的核心处理器是有限的,如果同一个进程下的线程过多,虽然在系统资源的分配上只需要执行一次,但是 CPU 需要并发处理的数量过多的话,反而会使得线程的执行的效率变低(CPU高速来回切换的处理线程)。

上文博主假设的qq 例子,应该是一个进程,然后不同的功能交由多线程来执行,功能之间可以独立并发执行,共同维护我们的qq。


三、 Java创建线程

Java 学习过程中主要是偏向于多线程开发,那么接下来学习的是如何用Java 代码来创建一个线程。

3.1 继承Thread 类

Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行了进一步的抽象和封装.

API : Application Programing linerface

给你一个软件,你能对他干什么,基于它提供的这些功能,就可以写一些代码,然后封装在一起,方便别人使用。

举个例子:

张三肚子饿了,想吃猪脚饭,自己做吧,首先得买猪脚,买调料,洗猪脚,切割,起锅烧油……想想都麻烦,于是张三打消了自己做饭的念头,于是前往楼下小吃店购买猪脚饭,张三到店之后,老板娘给张三一张纸,让他写一写自己想吃啥。

什么意思?我们想吃猪脚饭,不需要知道猪脚饭是怎么做的,我们只需要知道哪里有卖猪脚饭得地方即可

我们想创建一个线程,首先得找到 ”饭店“,这个饭店就是操作系统对创造线程操作封装的API ,然后JAVA 把 操作系统提供的 API 进一步的处理,封装成 Thread 类,我们不需要知道 系统是怎么创建一个线程的,只需要知道 Thread 类,可以吃到 "猪脚饭”。

代码实现:

class MyThread extends Thread {
    // MyThread 类继承 Thread 类,创建一个线程类
    // 并重写 Thread 类的 run() 方法
    // 此时MyThread 线程类的 run() 方法相当于 主线程中的 main() 方法
    @Override
    public void run() {

        while (true) {
            try {
                Thread.sleep(1000); //线程休眠,1000 = 1 秒
                System.out.println("t 线程 执行");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class Demo1 {
    public static void main(String[] args) {
        // 创建 MyThread 线程类实例化对象
        Thread t = new MyThread();
        // start 创建是新的线程并启动执行,调用run() 方法
        // 创建线程默认就会执行线程的 run 方法
        // 不调用 start() 方法线程不会启动
        t.start();

        while (true) {
            try {
                Thread.sleep(1000); //线程休眠,1000 = 1 秒
                System.out.println("Main主线程 执行");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

线程不调用 start() 方法线程不会启动

调用start() 方法会从系统中创建一个线程,新的线程会执行 run 方法,run 方法式线程的入口方法,类型于主线程的 main() 方法。

启动线程之后,线程就会进入就绪状态,随时准备被系统调度,CPU 执行。

两个线程中分别设置了死循环,打印线程执行,可以出看出控制台两个线程都可以打印数据,也就是说两个线程之间宏观上是并发执行的,线程执行的顺序是无序的。

MyThread 线程类的 run() 方法相当于 主线程中的 main() 方法,都是描述线程的入口。

疑问点:为什么调用 start() 方法会自动执行 run() 方法?

因为类Thread中的start方法中,调用了Thread中的run方法。
MyThread继承了Tread类,在MyThread 中重写run方法,就会覆盖掉Thread中的run方法,子类重写父类方法,优先调用子类重写后的方法,如果子类没有重写父类方法,默认执行的是父类的 run 方法,所以子类调用start方法后,实现的是自己的run方法体里面的代码。

3.2 实现 Runnable 接口

  1. 自定义一个类使其实现 Runnable 接口

class MyThread2 implements Runnable { //实现 Runnable 接口
    @Override
    public void run() { //重写接口里面的 run 方法
       //线程运行的代码
       while (true) {
           try {
               Thread.sleep(1000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           System.out.println("线程t执行");
       }
    }
}
  1. 创建 Thread 类实例, 调用 Thread 的构造方法时将 Runnable 对象作为 target 参数.

public class Demo2 {
    public static void main(String[] args) {
        //创建 Thread 类实例, 调用 Thread 的构造方法时将 Runnable 对象作为 target 参数.
        Thread t = new Thread(new MyThread2());
        t.start();// 启动线程

        while (true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("主线程 main 执行");
        }
    }
}

这是 Thread 线程类提供的有参构造方法,可以看出里面是 Runnable 接口的引用来接收,我们自定义的线程类由于实现了 Runnable 接口,此时发生向上转型,父接口引用接收子类对象,由于子类重写了 Runnable 接口的 run 方法,所以接口引用可以直接调用子类重写后的 run 方法,如果在 父类想调用子类其他独有的成员变量或者是方法,就需要 向下转型(强制类型转换)。

然后线程对象 t 调用 start() 方法启动线程然后调用 run() 方法。

采用实现接口方式创建一个线程 与 继承 Threard 类 创建一个线程 最终的结果是没什么区别的。


3.3 lambda 表达式创建线程

Thread t = new Thread(() -> {
      System.out.println("使用匿名类创建 Thread 子类对象");
});

t.start();

我们常常在通过创建 Thread 对象的时候,通过创建匿名内部类重写 run() 方法。

这里创建的匿名内部类

lambda 表达式的本质就匿名函数。

语法形式为 () -> {},其中 () 用来描述参数列表,{} 用来描述方法体,-> 为 lambda运算符 ,读作(goes to)。

有时候我们不是必须要自己重写某个匿名内部类的方法,我们可以利用 lambda表达式的接口快速指向一个已经被实现的方法。

public class Demo3 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread( () -> {
           while (true) {
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               System.out.println("线程 T执行");
           }
        });

        t.start(); //启动

        while (true) {
            Thread.sleep(1000);
            System.out.println("主线程Main执行");
        }
    }
}

至此,进程的基本认识博主已经分享完了,希望对大家有所帮助,如有不妥之处欢迎批评指正。

本期收录于博主的专栏——JavaEE,适用于编程初学者,感兴趣的朋友们可以订阅,查看其它“JavaEE基础知识”。

下期预告:Thread 类 以及 常见方法

感谢每一个观看本篇文章的朋友,更多精彩敬请期待:保护小周ღ *★,°*:.☆( ̄▽ ̄)/$:*.°★* ‘

猜你喜欢

转载自blog.csdn.net/weixin_67603503/article/details/129522228