【javaEE】多线程(初阶)Part1

目录

  • 前言
  • 一、【进程】回顾
  • 二、线程Thread
      • 5. 重要面试题: ==谈谈进程和线程之间的区别【高频】==(面试必考!背!!)
  • 三、创建线程
        • 【面试题:java中有哪些方式来创建线程?】
  • THINK


前言

少年的征程起点是心中的一团火

本文主要介绍的是【线程】相关内容,注意也要掌握【线程】相关代码的书写以及运行分析。


一、【进程】回顾

  1. 虽然多进程已经实现了并发编程,但是存在重要的问题:假如针对每一个客户端都分别创建进程,那么就会造成频繁创建/销毁进程,使得多进程就比较低效。

  2. 进程创建步骤:
    ① 创建PCB;
    ② 给进程分配资源(内存/文件),然后赋值到PCB中;
    (这对操作系统来说是不太容易的,比较消耗时间)
    ③ 把PCB插入链表

  3. 进程销毁步骤:
    ① 把PCB从链表上删除;
    ② 把PCB中持有的资源释放;
    ③ 销毁PCB


二、线程Thread

  1. 线程Thread
    ① 线程是被包含在进程中的。
    ② 一个进程默认会有一个线程,当然也可以有多个线程; 每个线程都是一个单独的“执行流”,可以单独在CPU上进行调度。
    ③ 同一个进程中的这些线程 共用同一份系统资源(内存+文件)

  2. 线程又被称为“轻量级进程”
    理由:创建线程的开销比创建进程小;且销毁线程的开销比销毁进程小。

  3. 使用线程:
    ① 能够更充分利用多核CPU,能够提高效率;
    ② 在同一个进程中,只是创建第一个线程的时候需要申请资源,后续再创建新的线程都是共用同一份资源(节省了申请资源的开销); 销毁线程的时候,只有销毁到最后一个线程的时候才真正释放资源,前面的线程销毁都不是真正的释放资源

  4. 操作系统的内核是通过一组PCB来描述一个进程的,每个PCB对应一个线程;
    ① 这组PCB上的内存指针和文件描述符表其实都是一样的,是共用的;
    ② 而状态、上下文、优先级以及记账信息则是每个PCB(每个线程)自己有一份。
    所以: 进程是 资源分配 的基本单位; 线程是 调度执行 的基本单位。

5. 重要面试题: 谈谈进程和线程之间的区别【高频】(面试必考!背!!)

答:① 进程包含线程;
② 线程比进程更轻量,创建更快、销毁也更快;
③ 同一个进程的多个线程之间共用一份内存和文件资源,而进程和进程之间则是独立的文件和内存资源;线程共用资源就省去了线程分配资源的过程
④ 进程是资源分配的基本单位,线程是调度执行的基本单位

  1. 多线程的线程数目不是越多越好,因为CPU核心数是有限的。当线程数目达到一定数量的时候,CPU的核心就已经被吃满了,此时再增加线程也无法提高效率了;反而会因为线程太多,线程调度开销太大而影响了效率。

  2. 现代的CPU一般都有“超线程技术”,如一个核心可以并行跑两个线程

  3. 如果两个线程同时修改同一个变量,也容易产生“线程不安全”问题。

  4. 如果某个线程发生了意外(出现异常),并且该异常没有处理好就可能导致这个进程崩溃,此时后续的所有线程都难以运行。

  5. 线程的一些相关操作在操作系统中都提供了一系列的API。
    (因为系统原生的线程的API是C语言的,所以java就把这些API给封装成java风格的了)

  6. 多个线程的执行是“抢占式执行”;线程是独立的执行流。

  7. 多线程的当前程序确实是跑起来了,但是如果想要查看是不是多个线程同时执行的,就可以借助jdk中的jconsole工具来进行查看

jconsole双击打开–> 本地进程–> 选择刚才运行的java进程(双击)–>
选择“不安全连接”(如果双击打开了jconsole之后啥都没有,则尝试 右键-以管理员方式运行)

① 有的机器上可能权限管理比较严格,导致jconsole查看不到进程信息,所以就使用“管理员权限运行”
② 打开查看线程的时候会发现除了自己运行的N个线程之外;还有别的线程,这是JVM自己创建的内置功能,有些是负责垃圾回收的、有些是用来辅助完成调试的、还有些是监控进程是否收到一些特殊信号并进行动作的
③ 这里的状态是java自己搞的状态,和操作系统中的PCB里的状态还不太一样;
此处的堆栈跟踪 :描述了当前线程、调用栈是啥样的;是方法之间相互调用的一个关联关系


三、创建线程

  1. 创建线程的办法
    ① 继承Thread类,重写run方法,run方法是新线程的入口
    ② 实现Runnable接口,重写run
    ③ 使用匿名内部类,实现创建Thread子类的方式
    ④ 使用匿名内部类,实现 实现Runnable接口的方式
    ⑤ 使用Lambda表达式(lambda本质上是一个“匿名函数”)
    (其实lambda表达式一般用于一个方法的实现上,该方法可以作为参数传入)
    线程的创建不不止这5中,还有两种后续介绍。
【面试题:java中有哪些方式来创建线程?】

(注:在实际工作中,在写代码时使用下划线将数字进行分割是一个有效技巧)

  1. 创建线程的相关事宜:
// 创建MyThread实例:创建实例并不会在系统中真的创建一个线程!!
MyThread myThread = new MyThread();
// 调用 start方法的时候才是真正创建出一个新的线程!!!
// 而此时新的线程就会执行run里面的逻辑,一直到run里的代码执行完新的线程才是运行结束。
myThread.start();
// 调用 start方法 其实是另外启动了一个线程来执行Thread中的run方法!
// 新的线程是一个单独的执行流,和现有线程的执行流不相关。 并发(并发+并行)执行

// 如果main方法里面没有Thread子类创建的实例对象调用start方法,则调用的其实是main里面自带的默认线程(也叫 主线程)
// main主线程(JVM创建)和MyThread创建出来的新线程是一个“并发执行”的关系。(但是此处的并发其实是指:并发+并行)
// 并发执行:两边同时执行,各自安好、互不干扰!
// MyThread的线程中run是该线程的入口方法
  1. 阻塞等待join
t1.start();
t2.start(); // 此处才创建线程

// 阻塞等待线程结束 join
// 在main中调用 t1.join(); 的效果其实就是 让main线程阻塞,一直到t1执行完run后main才继续执行!!!
// 在main中调用 t2.join(); 的效果其实就是 让main线程阻塞,一直到t2执行完run后main才继续执行!!!
// 注意:t1和t2是并发执行的(宏观上是同时执行的),而不是先执行完t1再执行t2!!

try { // 抛异常
    t1.join();
    t2.join();
} catch (InterruptedException e) {
    e.printStackTrace();
}

① 使用两个线程消耗的时间不一定是使用一个线程消耗的时间的50%。因为:其实两个线程的并发执行=并行+并发,在执行过程中,并行(其实会变快)和并发(总时间没有减少,反而会因为线程切换而调度开销变长)执行次数不确定
创建线程的实例的过程也是有开销的:串行执行没有额外创建线程,并发执行就需要额外创建线程。
如果计算量大、计算的久,创建线程的开销就不明显;但是如果计算量小、计算的快,创建线程的开销影响较大,多线程提升效率就不明显,反而可能会降低执行效率

线程Demo


THINK

重点掌握

  1. 线程的创建以及运行
  2. 面试题:进程和线程的区别
  3. 面试题:java中有哪些方式来创建线程

你可能感兴趣的:(Note-JavaEE,java,javaEE,多线程,进程,线程)