• 多线程基础:线程基本概念与线程的创建


    一. 线程与进程

    1. 进程

    一开始看线程与进程的概念可能会感觉到很抽象,晦涩难懂,先看下图:

    上图是WIN10系统中的任务管理器,操作系统的.exe文件可以理解成一个进程,可以这样说,进程是操作系统运行的一个基本运行单元。

    说到进程,有必要来说一下程序,程序由许多个指令与数据组成,就是指令序列。

    这些指令需要运行,数据需要读写,指令加载至CPU,让CPU做指定的任务。

    数据加载至内存,指令运行时,需要用到网络,磁盘等设备。这一过程就需要进程来加载指令,管理内存,处理IO的。

    所以当一个程序运行时,磁盘加载代码至内存就相当于开启一个进程。比如当Java每编译一次.class文件。就会创建一个JVM进程。

    我们可以将进程认为是程序运行的一个实例,并且大部分程序都可以运行多个进程实例,比如记事本,谷歌浏览器等,有的程序只能开一个线程,比如酷狗音乐等。

    2. 线程

    线程相当于进程中相互独立运行的子任务,这些子任务可以有多个, 它们共享着该进程所拥有的资源。

    在Java中进程是资源分配的最小单位,线程作为最小的调度单位,先有进程,后有线程。

    ☆二者对比总结☆

    1. 进程与进程之间是相互独立的,线程属于进程的一个子集存在于线程内。

    2. 进程共享系统资源,如内存,网络端口供其线程使用。

    3. 进程之间的通信较为复杂,本机通常使用IPC机制,不同计算机之间的通信通常使用Socket或者Http协议进行通信。

    4. 线程虽然比进程轻,但是线程的上下文切换的成本非常高。

    二. 并行与并发

    2.1 并发

    单任务运行环境下(单核CPU)线程执行方式是串行的,操作系统中的任务调度器,会将CPU的时间片分给不同程序去执行。

    同一时间段内,线程轮流执行的方式叫做并发【concurrent】。换句话说就是一段时间内,处理多件事情的需求。

    举个例子说明一下,什么是并发就会很好理解了:

    在单核CPU下,现在有两个线程需要执行, 但是在某个时刻下只能让一个线程获得CPU资源去运行,假设当线程1开始运行到指令3时,必须依赖线程2去执行。

    此时CPU会去执行线程2,而线程1必须等待线程2运行过后才可运行(大部分都是以时间片轮巡的方式)通过不断切换需要运行的线程的方式,这种就是并发【Parallel】指两个或多个事件在同一时间段内发生

    这个过程运行时间很快,我们很难感觉到,我们感觉好像是两个线程同时运行的,总结为一句话就是: 微观串行,宏观并行。

    2.2 并行

    上面说了,并发是一段时间内,处理多件事情的需求。如何解决这种需求,通过并行,所以可以说并行是解决并发的一种方式。

    关于并行的定义是这样:指两个或多个事件在同一时刻点同时发生。看下图:

    现在,在多核CPU下,以上两个线程不需要等待某一方执行完毕后再去执行,而是可以同时让两个线程同时运行,这就是并行。

    三. 同步与异步

    同步与异步的概念相对来说比较简单,我们从方法的角度去理解:

    假设有A与B两方法,调用A方法时必须返回调用后的结果才能执行B方法,这种就是同步。

    相反,无需等待结果返回直接就可以执行B方法就是异步

    多线程是异步的,关于多线程异步调用的应用有很多举几个:

    1. 较大文件的格式转换可以单开线程运行,防止阻塞。

    2. tomcat中的异步Servlet,让用户线程去处理耗时任务,防止tomcat阻塞。

    四. 创建线程

    4.1 继承Thread

    创建线程类之前,先看Thread类的构造:

    public class Thread implements Runnable {}

    Thread类实现Runable接口,Runable接口中只有一个抽象方法:

    1. @FunctionalInterface
    2. public interface Runnable {
    3. /**
    4. * When an object implementing interface <code>Runnable</code> is used
    5. * to create a thread, starting the thread causes the object's
    6. * <code>run</code> method to be called in that separately executing
    7. * thread.
    8. * <p>
    9. * The general contract of the method <code>run</code> is that it may
    10. * take any action whatsoever.
    11. *
    12. * @see java.lang.Thread#run()
    13. */
    14. public abstract void run();
    15. }

    所以继承Thread类,需要重写其中的run()方法,将线程所处理的任务放到run()方法执行体中,然后通过创建该类的对象,执行start()方法,才是真正意义上的开启一个线程。

    具体代码:

    1. /**
    2. * MyThread是个线程类
    3. * @author 爪哇斗罗(javaDouluo)
    4. *
    5. */
    6. public class MyThread extends Thread {
    7. /**
    8. * 重写run方法
    9. */
    10. @Override
    11. public void run() {
    12. System.out.println("MyRunThread===>" + Thread.currentThread().getName());
    13. }
    14. public static void main(String[] args) {
    15. MyThread myThread = new MyThread();
    16. // 开启线程
    17. myThread.start();
    18. // 当前线程名
    19. System.out.println("main===>"+ Thread.currentThread().getName());
    20. }
    21. }

    运行结果:

    注意:

    上面有两个线程,一个是主线程,还有一个是我们自己创建的线程,不要认为线程的执行顺序是按照代码的放置顺序来执行的,这与你调用线程方法start()的放置顺序无关。

    4.2 实现Runable接口

    当出现线程类已经有一个父类时,继承Thread方法就不可行,可以使用实现Runable接口的方式来创建线程,就是对继承的一种拓展。

    1. /**
    2. * @author 爪哇斗罗(javaDouluo)
    3. * @date 2022年06月25日 0:17
    4. */
    5. public class MyRunable implements Runnable{
    6. /**
    7. * 实现Run方法
    8. */
    9. @Override
    10. public void run() {
    11. System.out.println("MyRunThread===>" + Thread.currentThread().getName());
    12. }
    13. public static void main(String[] args) throws InterruptedException {
    14. // 实例化当前线程对象
    15. Runnable runnable = new MyRunable();
    16. // 将当前线程对象入参至Thread
    17. Thread thread = new Thread(runnable);
    18. // 开启线程
    19. thread.start();
    20. // 当前线程名
    21. System.out.println("main===>" + Thread.currentThread().getName());
    22. }
    23. }

    这种方式的结果与上述是一样的。

    4.3 FutureTask配合Thread

    当线程结束之后我们需要返回一个结果可以使用FutureTask来接收一个Callable类型的参数来处理有返回结果的情况。

    1. package com.jektong.thread.day01;
    2. import java.util.concurrent.Callable;
    3. import java.util.concurrent.ExecutionException;
    4. import java.util.concurrent.FutureTask;
    5. import java.util.concurrent.TimeUnit;
    6. /**
    7. * @author 爪哇斗罗(javaDouluo)
    8. * @date 2022年06月27日 23:10
    9. */
    10. public class FutureTaskThread {
    11. /**
    12. * FutureTask通过配合Callable来实现有返回值得效果
    13. * @param args
    14. */
    15. public static void main(String[] args) throws ExecutionException, InterruptedException {
    16. FutureTask<Integer> integerFutureTask = new FutureTask<>(new Callable<Integer>() {
    17. @Override
    18. public Integer call() throws Exception {
    19. System.out.println("java");
    20. TimeUnit.SECONDS.sleep(5);
    21. return 0;
    22. }
    23. });
    24. Thread thread = new Thread(integerFutureTask);
    25. thread.start();
    26. // 获取返回值
    27. integerFutureTask.get();
    28. }
    29. }

  • 相关阅读:
    hi mate, lets recall the bloody “JOIN“
    怎样提取视频中的音频?分享一个一学就会的方法~
    回溯算法总结
    零基础可以报考中级经济师吗?需要准备多久?
    CocosCreator使用 ProtoBuf WebSocket与服务器对接方法
    LocalDateTime获取月份第一天和最后一天
    《C++避坑神器·十八》运算符重载,小白也能看懂
    四大模型板块更新!你感兴趣的都在这里...
    研发费用补贴政策和条件是什么?
    Power BI: DAX 中 IN 和 NOT IN 的用法
  • 原文地址:https://blog.csdn.net/qq_41857955/article/details/125324831