一开始看线程与进程的概念可能会感觉到很抽象,晦涩难懂,先看下图:
上图是WIN10系统中的任务管理器,操作系统的.exe文件可以理解成一个进程,可以这样说,进程是操作系统运行的一个基本运行单元。
说到进程,有必要来说一下程序,程序由许多个指令与数据组成,就是指令序列。
这些指令需要运行,数据需要读写,指令加载至CPU,让CPU做指定的任务。
数据加载至内存,指令运行时,需要用到网络,磁盘等设备。这一过程就需要进程来加载指令,管理内存,处理IO的。
所以当一个程序运行时,磁盘加载代码至内存就相当于开启一个进程。比如当Java每编译一次.class文件。就会创建一个JVM进程。
我们可以将进程认为是程序运行的一个实例,并且大部分程序都可以运行多个进程实例,比如记事本,谷歌浏览器等,有的程序只能开一个线程,比如酷狗音乐等。
线程相当于进程中相互独立运行的子任务,这些子任务可以有多个, 它们共享着该进程所拥有的资源。
在Java中进程是资源分配的最小单位,线程作为最小的调度单位,先有进程,后有线程。
☆二者对比总结☆
1. 进程与进程之间是相互独立的,线程属于进程的一个子集存在于线程内。
2. 进程共享系统资源,如内存,网络端口供其线程使用。
3. 进程之间的通信较为复杂,本机通常使用IPC机制,不同计算机之间的通信通常使用Socket或者Http协议进行通信。
4. 线程虽然比进程轻,但是线程的上下文切换的成本非常高。
单任务运行环境下(单核CPU)线程执行方式是串行的,操作系统中的任务调度器,会将CPU的时间片分给不同程序去执行。
同一时间段内,线程轮流执行的方式叫做并发【concurrent】。换句话说就是一段时间内,处理多件事情的需求。
举个例子说明一下,什么是并发就会很好理解了:
在单核CPU下,现在有两个线程需要执行, 但是在某个时刻下只能让一个线程获得CPU资源去运行,假设当线程1开始运行到指令3时,必须依赖线程2去执行。
此时CPU会去执行线程2,而线程1必须等待线程2运行过后才可运行(大部分都是以时间片轮巡的方式)通过不断切换需要运行的线程的方式,这种就是并发【Parallel】:指两个或多个事件在同一时间段内发生。
这个过程运行时间很快,我们很难感觉到,我们感觉好像是两个线程同时运行的,总结为一句话就是: 微观串行,宏观并行。
上面说了,并发是一段时间内,处理多件事情的需求。如何解决这种需求,通过并行,所以可以说并行是解决并发的一种方式。
关于并行的定义是这样:指两个或多个事件在同一时刻点同时发生。看下图:
现在,在多核CPU下,以上两个线程不需要等待某一方执行完毕后再去执行,而是可以同时让两个线程同时运行,这就是并行。
同步与异步的概念相对来说比较简单,我们从方法的角度去理解:
假设有A与B两方法,调用A方法时必须返回调用后的结果才能执行B方法,这种就是同步。
相反,无需等待结果返回直接就可以执行B方法就是异步。
多线程是异步的,关于多线程异步调用的应用有很多举几个:
1. 较大文件的格式转换可以单开线程运行,防止阻塞。
2. tomcat中的异步Servlet,让用户线程去处理耗时任务,防止tomcat阻塞。
创建线程类之前,先看Thread类的构造:
public class Thread implements Runnable {}
Thread类实现Runable接口,Runable接口中只有一个抽象方法:
- @FunctionalInterface
- public interface Runnable {
- /**
- * When an object implementing interface <code>Runnable</code> is used
- * to create a thread, starting the thread causes the object's
- * <code>run</code> method to be called in that separately executing
- * thread.
- * <p>
- * The general contract of the method <code>run</code> is that it may
- * take any action whatsoever.
- *
- * @see java.lang.Thread#run()
- */
- public abstract void run();
- }
所以继承Thread类,需要重写其中的run()方法,将线程所处理的任务放到run()方法执行体中,然后通过创建该类的对象,执行start()方法,才是真正意义上的开启一个线程。
具体代码:
- /**
- * MyThread是个线程类
- * @author 爪哇斗罗(javaDouluo)
- *
- */
- public class MyThread extends Thread {
-
- /**
- * 重写run方法
- */
- @Override
- public void run() {
- System.out.println("MyRunThread===>" + Thread.currentThread().getName());
- }
-
- public static void main(String[] args) {
- MyThread myThread = new MyThread();
- // 开启线程
- myThread.start();
- // 当前线程名
- System.out.println("main===>"+ Thread.currentThread().getName());
- }
- }
运行结果:
注意:
上面有两个线程,一个是主线程,还有一个是我们自己创建的线程,不要认为线程的执行顺序是按照代码的放置顺序来执行的,这与你调用线程方法start()的放置顺序无关。
当出现线程类已经有一个父类时,继承Thread方法就不可行,可以使用实现Runable接口的方式来创建线程,就是对继承的一种拓展。
- /**
- * @author 爪哇斗罗(javaDouluo)
- * @date 2022年06月25日 0:17
- */
- public class MyRunable implements Runnable{
- /**
- * 实现Run方法
- */
- @Override
- public void run() {
- System.out.println("MyRunThread===>" + Thread.currentThread().getName());
- }
-
- public static void main(String[] args) throws InterruptedException {
- // 实例化当前线程对象
- Runnable runnable = new MyRunable();
- // 将当前线程对象入参至Thread
- Thread thread = new Thread(runnable);
- // 开启线程
- thread.start();
- // 当前线程名
- System.out.println("main===>" + Thread.currentThread().getName());
- }
- }
这种方式的结果与上述是一样的。
当线程结束之后我们需要返回一个结果可以使用FutureTask来接收一个Callable类型的参数来处理有返回结果的情况。
- package com.jektong.thread.day01;
-
- import java.util.concurrent.Callable;
- import java.util.concurrent.ExecutionException;
- import java.util.concurrent.FutureTask;
- import java.util.concurrent.TimeUnit;
-
- /**
- * @author 爪哇斗罗(javaDouluo)
- * @date 2022年06月27日 23:10
- */
- public class FutureTaskThread {
- /**
- * FutureTask通过配合Callable来实现有返回值得效果
- * @param args
- */
- public static void main(String[] args) throws ExecutionException, InterruptedException {
- FutureTask<Integer> integerFutureTask = new FutureTask<>(new Callable<Integer>() {
- @Override
- public Integer call() throws Exception {
- System.out.println("java");
- TimeUnit.SECONDS.sleep(5);
- return 0;
- }
- });
- Thread thread = new Thread(integerFutureTask);
- thread.start();
- // 获取返回值
- integerFutureTask.get();
- }
- }