🏆今日学习目标:线程和进程
😃创作者:颜颜yan_
✨个人主页:颜颜yan_的个人主页
⏰本期期数:第三期
🎉专栏系列:JAVA
前言
多线程是提升程序性能非常重要的一种方式,也是Java 编程中的一项重要技术。在程序设计中,多线程就是指一个应用程序中有多条并发执行的线索,每条线索都被称作—个线程,它们会交替执行,彼此可以通信。
一、进程与线程
1.进程
进程(process) 是计算机中程序的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
虽然进程在程序执行时产生,但进程并不是程序。程序是“死”的,进程是“活”的。程序是指编译好的二进制文件,它存放在磁盘上,不占用系统资源,是具体的;而进程存在于内存中,占用系统资源,是抽象的。当一次程序执行结束时,进程随之消失,进程所用的资源被系统回收。
对计算机用户而言,计算机似乎能够同时执行多个进程,如听音乐、玩游戏、语音聊天等等,都能在同一台计算机上同时进行。但实际上,一个单核的 CPU 同一时刻只能处理一个进程,用户之所以认为同时会有多个进程在运行,是因为计算机系统采用了多道程序设计技术。
所谓多道程序设计,是指计算机允许多个相互独立的程序同时进人内存,在内存的管理控制之下,相互之间穿插运行。多道程序设计必须有硬件基础作为保障。
采用多道程序设计的系统,会将 CPU 的周期划分为长度相同的时间片,在每个 CPU时间片内只处理一个进程,也就是说,在多个时间片内,系统会让多个进程分时使用 CPU。假如现在内存中只有3个进程A、B、C,那么CPU 时间片的分配情况大致如下图。
虽然在同一个时问片中,CPU 只能处理一个进程,但 CPU 划分的时间片是非常微小的,且 CPU 运行速度极快(1 秘可执行约10亿条指令),因此,在宏观上并发执行多个程序、处理多个进程。
进程对 CPU 的使用权是由操作系统内核分配的,操作系统内核必须知道内存中有多少个进程,并且知道此时正在使用 CPU 的进程,这就要求内核必须能够区分进程,并可获取进程的相关属性。
2.线程
通过上述节的介绍可以知道,每个运行的程序都是一个进程,在一个进程中还可以有多个执行单元同时运行,这些执行单元可以看作程序执行的线程(thread)。每一个进程中都至少存在一个线程。例如,当一个Java 程序启动时,就会产生一个进程,该进程默认创建一个线程,这个线程会运行main()方法中的代码。
在程序中,代码都是按照调用顺序依次往下执行的,没有出现两段程序代码交替运行的效果,这样的程序称作单线程程序。如果希望程序中实现多段程序代码交替运行的效果,则需要创建多个线程,即多线程程序。所谓多线程是指一个进程在执行过程中可以产生多个线程,这些线程在运行时是相互独立的,它们可以并发执行。多线程程序的执行过程如图 。
图中的多条线程看起来是同时执行的;其实不然,图中多线程程序的执行过程它们和进程一样,也是由 CPU 轮流执行的,只不过 CPU 运行速度很快,因此给人同时执行的感觉。
二、线程的创建
Java 提供厂3种多线程的创建方式:
(1)继承 java.lang 包中的 Thread 类,重写 Thread 类的run()方法,在run()方法中实现多线程代码。
(2)实现 java.lang.Runnable 接口,在run()方法中实现多线程代码。
(3)实现 java.util.concurrent.Callable 接口,重写 callO方法,并使用 Future 接口获取call()方法返回的结果。
2.1 继承Thread类创建多线程
Main类
public class Main { public static void main(String[] args) { MyThread1 myThread = new MyThread1(); //创建MyThread1类的实例对象 myThread.run(); //调用Mythread1类的run方法 while (true){ //死循环,输出信息 System.out.println("main()方法在运行"); } } }
MyThread1类
public class MyThread1 { public void run(){ while (true){ //死循环,输出信息 System.out.println("MyThread类的run方法在运行"); } } }
运行结果如图:
从图中可以看出,程序一直打印“My Thread 类的run()方法在运行”,这是因为该程序是一个单线程程序。在第一幅图的代码调用 MyThread1 类的run()方法时,执行第 MyThread1 类代码定义的死循环,因此,MyThread 类的 printin 语句将一直执行,而main()方法中的 printin 语句无法得到执行。
如果希望两个while 循环中的println 语句能够并发执行,就需要实现多线程。为此Java 提供了线程类 Thread。通过继承 Thread 类,并重写 Thread 类中的run()方法,便可实现生线程。在Thread 类中提供了 start()方法用于启动新线程。新线程启动后,Java虚拟机会自动调用 run()方法;如果子类重写了run()方法,便会执行子类中的run()方法。
2.2 实现Runnable接口创建多线程
上面通过继承 Thread 类实现了多线程,但是这种方式有一定的局限性。因为Java 只支持单继承,一个类一旦继承了某个父类,就无法再继承 Thread 类。例如,Studert类继承了Person 类,那么Student 类就无法再通过继承 Thread 类创建线程。
为了克服这种弊端,Thread 类提供了另一个构造方法——Thread (Runnable target),其中参数类型 Runnable 是一个接口,它具有一个run()方法。通过Thread(Runnabiletarget)构造方法创建线程对象时,只需为该方法传递一个实现了 Runnable 接口的对象,这样,创建的线程将实现 Runnable 接口中的run()方法作为运行代码,而不需要调用 Thread类中的run()方法。
下面通过案例演示如何通过实现 Runnable 接口的方式创建多线程,如图所示:
Main类
public class Main { public static void main(String[] args) { MyThread2 myThread2 = new MyThread2(); //创建MyThread2类的实例对象 Thread thread = new Thread(); // 创建线程对象 thread.start(); //开启线程,执行线程中的run()方法 while (true){ System.out.println("main()方法在运行"); } } }
MyThread2类
public class MyThread2 implements Runnable{ public void run() { //线程的代码段,当调用start()方法时,线程从此开始执行 while (true){ System.out.println("MyThread类的run()方法在运行"); } } }
在代码中,代码定义的 MyThreado2 类实现了 Runnable 接口,并在中代码中重写了 Runnable 接口中的run()方法。在Main代码通过调用Thread类的构造方法将NyThread2类的实例对象作为参数传入。代码中调用 strar()方法开启新线程执行 MyThread2 关中的代码。而主线程继续执行 main()方法中的代码。
运行结果如图:
从图中可以看出来,main()和MyThread2类中的run()方法都被执行了,说明实现了多线程
2.3 实现Callable接口创建多线程
通过 Thread 类和 Runnable 接口实现多线程时,需要重写run()方法,但是由于run()f方法没有返回值,无法从新线程中获取返回结果。为了解决这个问题,Java 提供了Callable接口来满足这种既能创建新线程又有返回值的需求。
通过实现 Callable 接口的方式创建并启动线程的主要步骤如下:
(1)创建 Callable 接口的实现类,同时重写 Callable 接口的call()方法。
(2)创建 Callable 接口的实现类对象。
(3)通过线程结果处理类 FutureTask 的有参构造方法封装 Callable 接口的实现类对象。
(4)调用参数为 FutureTask 类对象的有参构造方法 Thread() 创建 Tbread 线程实例。
(5)调用线程实例的 start()方法启动线程。
下面通过一个案列演示如何通过买现 Callable 接口的方式来实现多线程,如下面文件所示。
Main
import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class Main { public static void main(String[] args) throws ExecutionException, InterruptedException { Mythread3 myThread = new Mythread3(); //创建Callable接口类的实例对象 //使用FuntureTask封装Mythread3类 FutureTask ft1 = new FutureTask(myThread); //使用Thread(Runnable target,String name)构造方法创建线程对象 Thread thread1 = new Thread(ft1,"thread"); //调用线程对象的start()方法启动线程 thread1.start(); //通过FuntureTaskdioxin的方法管理返回值 System.out.println(Thread.currentThread().getName()+"的返回结果:"+ft1.get()); int a=0; while (a++<5){ System.out.println("main()方法在运行"); } } }
MyThread3类
import java.util.concurrent.*; public class Mythread3 implements Callable { //重写Callable接口中的call()方法 public Object call() throws Exception{ int i=0; while (i++<5){ System.out.println(Thread.currentThread().getName()+"的call()方法在运行"); } return i; } }
在上述文件中,在MyThread3类代码定义了一个实现 Callable 接口的实现类,并在 Calalble接口中重写了call()方法。在Main中,代码创建了 Callable接口的实例,并调用有参的Thread()构造方法创建了线程对象thread1。在代码中调用线程对象 thread1的 start()方法启动线程。
运行结果如图所示:
从图可以看出,所示的案例通过实现 Callable 接口的方式实现了多线程开且有返回结果。
Callable 接口方式实现的多线程是通过 Future Task 类来封装和管理返回结果的,Future Task 类的直接父接口是RunnableFuture,从名称上可以看出 RunnableFuture 是Runnable 和Future的结合体。
总结
以上就是今天的学习内容啦~
如果有兴趣的话可以订阅专栏,持续更新呢~
咱们下期再见~