• 多线程的相关知识


    目录

    一、进程和线程

    1、程序

    2、进程

    3、线程

    二、线程的认识

    1、线程的示例

     2、并行和并发的区别

     3、通过jconsole命令查看线程

    三、创建线程

    1、继承Thread类,覆写run方法

    2、覆写Runnable接口,覆写run方法

     3、关于前两种的变形写法

    4、覆写Callable接口,覆写call方法

    5、使用线程池创建线程

    6、感受多线程和顺序执行的执行速度差异

    三、Thread类的常见方法

    1、构造方法

    2、Therad类的核心属性

    3、启动线程用的是Thread类的start方法

    4、线程的中断方法 

    (1)通过共享变量中断线程 

    (2)使用Thread.interrupted()静态方法或者Thread对象的成员方法isInterrupted()

     5、线程收到内置的中断通知方式

    6、等待一个线程 

     7、获取正在执行的线程对象

    8、休眠当前线程 

    四、线程的所有状态 

    1、多线程的状态获取

     2、线程状态和状态转移的意义

     3、线程的新建,运行和终止状态

    4、三种堵塞状态

     5、从运行态转到就绪态(yield)

    6、线程的重复启动引发的问题

    五、相关代码


    一、进程和线程

    1、程序

    一系列有组织的文件,封装操作系统的各种API,实现不同的效果,比如qq

    2、进程

    程序在系统中的一次执行过程,进程是现代操作系统的中资源分配(CPU、内存等关键系统资源)的最小单位

    IP地址:唯一在网络上表示一台主机的位置,本机IP:127.0.0.1

    通过IP地址找到主机,然后通过端口号找到某一个进程,端口号在操作系统上唯一的定位一个进程

    3、线程

    现代的操作系统都是多线程操作系统,线程是进程中的一个独立的任务,比如说打开一个qq音乐就是一个进程,播放音乐和下载音乐就是两个线程,同一个进程的所有线程共享进程的资源,线程是操作系统的基本单位。

    二、线程的认识

    1、线程的示例

    2、并行和并发的区别

     并发是指一个处理器同时处理多个任务。
     并行是指多个处理器或者是多核的处理器同时处理多个不同的任务。
     并发是逻辑上的同时发生(simultaneous),而并行是物理上的同时发生。

       来个比喻:并发是一个人同时吃三个馒头,而并行是三个人同时吃三个馒头。

     3、通过jconsole命令查看线程

    三、创建线程

    1、继承Thread类,覆写run方法

    ①一个子类继承Thread类

    ②覆写run方法

    ③产生当前的子类对象,而后调用start方法启动线程

    1. /**
    2. * 继承Thread类,覆写run方法
    3. */
    4. public class ThreadMethod1 extends Thread {
    5. @Override
    6. public void run() {
    7. System.out.println("这是子线程的输出结果~~");
    8. System.out.println("这是子线程的输出结果~~");
    9. System.out.println("这是子线程的输出结果~~");
    10. }
    11. public static void main(String[] args) {
    12. //创建线程类的对象
    13. ThreadMethod1 m1=new ThreadMethod1();
    14. //启动线程
    15. m1.start();
    16. System.out.println("主线程的输出语句~~");
    17. }
    18. }

     

     

    调用start方法启动线程,是由JVM产生操作系统的线程并启动,到底什么时候开始启动,对于我们来说是不可见的,所以在多线程的结果这块来说是多变的。

    2、覆写Runnable接口,覆写run方法

    ①实现Runnable接口

    ②创建线程的任务对象

    ③创建线程对象,将任务对象传入线程对象

    ④启动线程

    1. /**
    2. * 覆写Runnable接口,覆写run方法
    3. * 这个实现了Runnable接口的子类,并不是直接的线程对象,只是一个线程的核心工作任务
    4. * 线程的任务和线程实体的关系
    5. */
    6. public class RunnableThread implements Runnable{
    7. @Override
    8. public void run() {
    9. System.out.println("这是Runnable方式实现的子线程任务~~");
    10. }
    11. public static void main(String[] args) {
    12. //创建线程的任务对象
    13. RunnableThread runnableThread=new RunnableThread();
    14. //创建线程对象,将任务对象传入线程对象
    15. Thread thread=new Thread(runnableThread);
    16. //启动线程
    17. thread.start();
    18. System.out.println("主线程的输出语句~~");
    19. }
    20. }

     3、关于前两种的变形写法

    匿名内部类继承了Thread类,然后实现了run方法

    1. /**
    2. * 前两种方法的变形
    3. */
    4. public class OtherThread {
    5. public static void main(String[] args) {
    6. //匿名内部类继承Thread类
    7. Thread t1=new Thread(){
    8. @Override
    9. public void run() {
    10. System.out.println("匿名内部类继承Thread类~~");
    11. System.out.println(Thread.currentThread().getName());
    12. }
    13. };
    14. t1.start();
    15. System.out.println("这是主线程"+Thread.currentThread().getName());
    16. }
    17. }

    使用匿名内部类实现Runnable接口

    1. /**
    2. * 使用匿名内部类实现Runnable接口
    3. */
    4. public class OtherThread{
    5. public static void main(String[] args) {
    6. Thread thread=new Thread(new Runnable() {
    7. @Override
    8. public void run() {
    9. System.out.println("匿名内部类实现Runnable接口~~");
    10. System.out.println(Thread.currentThread().getName());
    11. }
    12. });
    13. thread.start();
    14. System.out.println("这是主线程"+Thread.currentThread().getName());
    15. }
    16. }

     使用Lambda表达式实现Runnable接口

    1. /**
    2. * 使用Lambda表达式实现Runnable接口~~
    3. */
    4. public class OtherThread{
    5. public static void main(String[] args) {
    6. Thread t1=new Thread(()-> System.out.println("Lambda表达式实现Runnable接口~~"));
    7. t1.start();
    8. System.out.println("这是主线程"+Thread.currentThread().getName());
    9. }
    10. }

    4、覆写Callable接口,覆写call方法

    5、使用线程池创建线程

    6、感受多线程和顺序执行的执行速度差异

    执行10亿个数字的累加

    顺序执行:先+10亿,再加10亿,最终个都是在主线程中+20亿

    并发执行:子线程+10亿,主线程+10亿,最终是主线程和子线程并发的执行+10亿

    在理论上来看,并发执行的耗时是顺序执行的一半

    1. package Thread;
    2. /**
    3. * 顺序(串行)和并发的对比
    4. */
    5. public class Thread666 {
    6. public static void main(String[] args) throws InterruptedException {
    7. serial();
    8. concurrent();
    9. }
    10. private static final long count=10_0000_0000;
    11. //并发执行+10亿数据操作
    12. private static void concurrent() throws InterruptedException {
    13. Long start=System.nanoTime();
    14. Thread thread=new Thread(()->{
    15. //子线程进行+10亿操作
    16. long a=0;
    17. for (int i = 0; i <count ; i++) {
    18. a++;
    19. }
    20. });
    21. thread.start();
    22. //主线程执行+10亿操作
    23. long b=0;
    24. for (int i = 0; i < count; i++) {
    25. b++;
    26. }
    27. //等待子线程执行结束
    28. thread.join();
    29. long end=System.nanoTime();
    30. double alltime=(end-start)*1.0/1000/1000;
    31. System.out.println("并发执行共耗时"+alltime+"ms");
    32. }
    33. //串行进行20亿的累加
    34. private static void serial(){
    35. long start=System.nanoTime();
    36. long a=0;
    37. for (int i = 0; i < count; i++) {
    38. a++;
    39. }
    40. //下面的执行必须要等到上面的执行完毕才可以继续
    41. long b=0;
    42. for (int i = 0; i < count; i++) {
    43. b++;
    44. }
    45. long end=System.nanoTime();
    46. double alltime=(end-start)*1.0/1000/1000;
    47. System.out.println("顺序执行共耗时"+alltime+"ms");
    48. }
    49. }

     多线程最大的应用场景就是把一个打的任务拆分为多个子任务,多个子线程并发执行,提高系统的处理效率

    三、Thread类的常见方法

    无论是继承Thread类还是实现Runnable接口,最终启动线程都是调用Thread类的start方法

    Thread类就是JVM描述管理线程的类,每个线程都对应唯一的一个Thread对象

    1、构造方法

    1. public class NameThread {
    2. public static void main(String[] args) {
    3. Thread t1=new Thread();
    4. Thread t2=new Thread(new Runnable() {
    5. @Override
    6. public void run() {
    7. System.out.println("传入Runnable对象~~");
    8. }
    9. });
    10. Thread t3=new Thread("线程三");
    11. t3.setName("线程三");
    12. Thread t4=new Thread(new Runnable() {
    13. @Override
    14. public void run() {
    15. System.out.println("传入Runnable对象~~");
    16. }
    17. },"线程四");
    18. }
    19. }

    2、Therad类的核心属性

    1. /**
    2. * 线程的常用属性
    3. */
    4. public class propertyTherad {
    5. public static void main(String[] args) {
    6. Thread thread=new Thread(new Runnable() {
    7. @Override
    8. public void run() {
    9. for (int i = 0; i <10 ; i++) {
    10. System.out.println(Thread.currentThread().getName()+"我还活着~~");
    11. try {
    12. Thread.sleep(1000);
    13. } catch (InterruptedException e) {
    14. throw new RuntimeException(e);
    15. }
    16. }
    17. System.out.println(Thread.currentThread().getName()+"我即将嘎了~~");
    18. }
    19. },"测试线程");
    20. System.out.println(Thread.currentThread().getName()+"ID:"+thread.getId());
    21. System.out.println("状态:"+Thread.currentThread().getState());
    22. System.out.println("优先级:"+Thread.currentThread().getPriority());
    23. System.out.println("是否为后台线程:"+Thread.currentThread().isDaemon());
    24. System.out.println("是否存活:"+Thread.currentThread().isAlive());
    25. System.out.println("子线程是否存活:"+thread.isAlive());
    26. thread.start();
    27. }
    28. }

    3、启动线程用的是Thread类的start方法

    查看上述示例即可

    4、线程的中断方法 

           李四一旦进到工作状态,他就会按照行动指南上的步骤去进行工作,不完成是不会结束的。但有时我们 需要增加一些机制,例如老板突然来电话了,说转账的对方是个骗子,需要赶紧停止转账,那张三该如 何通知李四停止呢?这就涉及到我们的中断线程的方式了。

           中断一个·正在执行的线程(run还没有执行结束),普通线程会在run方法执行结束后自动停止。 

    (1)通过共享变量中断线程 

    1. /**
    2. * 通过共享变量中断线程
    3. */
    4. public class shared_variable {
    5. private static class MyThread extends Thread {
    6. //多个线程都会用到的变量加上volatile关键字
    7. //表示当前变量是否需要停止
    8. volatile boolean isQuit=false;
    9. @Override
    10. public void run() {
    11. while (!isQuit){
    12. System.out.println(Thread.currentThread().getName()+"我一直都在~~");
    13. try {
    14. Thread.sleep(1000);
    15. } catch (InterruptedException e) {
    16. throw new RuntimeException(e);
    17. }
    18. }
    19. System.out.println(Thread.currentThread().getName()+"我要没了~~");
    20. }
    21. }
    22. public static void main(String[] args) throws InterruptedException {
    23. MyThread m1=new MyThread();
    24. Thread thread=new Thread(m1,"线程一~~");
    25. System.out.println("我还在哦~~");
    26. thread.start();
    27. //让主线程暂停3秒,中断子线程
    28. Thread.sleep(3000);
    29. System.out.println("我要走了哦~~");
    30. m1.isQuit=true;
    31. }
    32. }

    (2)使用Thread.interrupted()静态方法或者Thread对象的成员方法isInterrupted()

    1. /**
    2. * 使用Thread.interrupted()静态方法
    3. */
    4. public class property {
    5. private static class MyRunnable implements Runnable {
    6. @Override
    7. public void run() {
    8. //静态方法,判断当前线程是否被中断
    9. while (!Thread.interrupted()) {
    10. System.out.println(Thread.currentThread().getName() + "别烦我,在打游戏呢~~~");
    11. try {
    12. Thread.sleep(1000);
    13. } catch (InterruptedException e) {
    14. //当线程中断时,会抛出中断异常
    15. System.out.println("有个老六在偷家~~~");
    16. break;
    17. }
    18. }
    19. System.out.println(Thread.currentThread().getName()+"额,水晶被偷了~~~");
    20. }
    21. }
    22. public static void main(String[] args) throws InterruptedException {
    23. MyRunnable m1=new MyRunnable();
    24. Thread thread=new Thread(m1,"线程一~~");
    25. System.out.println("正在快乐的吃兵线中~~~");
    26. thread.start();
    27. //让主线程暂停5秒,中断子线程
    28. Thread.sleep(5000);
    29. thread.interrupt();
    30. }
    31. }

    1. /**
    2. * Thread对象的成员方法isInterrupted()
    3. */
    4. public class property {
    5. private static class MyRunnable implements Runnable {
    6. @Override
    7. public void run() {
    8. //成员方法,判断当前线程是否中断
    9. while (!Thread.currentThread().isInterrupted()) {
    10. System.out.println(Thread.currentThread().getName() + "别烦我,在打游戏呢~~~");
    11. try {
    12. Thread.sleep(1000);
    13. } catch (InterruptedException e) {
    14. //当线程中断时,会抛出中断异常
    15. System.out.println("有个老六在偷家~~~");
    16. break;
    17. }
    18. }
    19. System.out.println(Thread.currentThread().getName()+"额,水晶被偷了~~~");
    20. }
    21. }
    22. public static void main(String[] args) throws InterruptedException {
    23. MyRunnable m1=new MyRunnable();
    24. Thread thread=new Thread(m1,"线程一~~");
    25. System.out.println("正在快乐的吃兵线中~~~");
    26. thread.start();
    27. //让主线程暂停5秒,中断子线程
    28. Thread.sleep(5000);
    29. thread.interrupt();
    30. }
    31. }

     5、线程收到内置的中断通知方式

    thread 收到通知的方式有两种:

    ① 如果线程因为调用 wait/join/sleep 等方法而阻塞挂起,则以 InterruptedException 异常的形式通 知,清除中断标志

            当出现 InterruptedException 的时候, 要不要结束线程取决于 catch 中代码的写法. 可以选择 忽略这个异常, 也可以跳出循环结束线程.

    ②否则,只是内部的一个中断标志被设置,thread 可以通过

          Thread.interrupted() 判断当前线程是否被中断,若中断状态为true,清除中断标志

    1. /**
    2. * Thread.interrupted() 判断当前线程是否被中断,若中断状态为true,清除中断标志
    3. */
    4. public class interrupt_inform {
    5. private static class MyRUnnable implements Runnable{
    6. @Override
    7. public void run() {
    8. for (int i = 0; i <5 ; i++) {
    9. System.out.println(Thread.interrupted());
    10. }
    11. }
    12. }
    13. public static void main(String[] args) {
    14. MyRUnnable m1=new MyRUnnable();
    15. Thread thread=new Thread(m1);
    16. thread.start();
    17. thread.interrupt();
    18. }
    19. }

          Thread.currentThread().isInterrupted() 判断指定线程是否被中断,不清除中断标志 这种方式通知收到的更及时,即使线程正在 sleep 也可以马上收到。

    1. /**
    2. * Thread.currentThread().isInterrupted() 判断指定线程是否被中断
    3. * 不清除中断标志 这种方式通知收到的更及时,即使线程正在 sleep 也可以马上收到。
    4. */
    5. public class interrupt_inform {
    6. private static class MyRUnnable implements Runnable{
    7. @Override
    8. public void run() {
    9. for (int i = 0; i <5 ; i++) {
    10. System.out.println(Thread.currentThread().isInterrupted());
    11. }
    12. }
    13. }
    14. public static void main(String[] args) {
    15. MyRUnnable m1=new MyRUnnable();
    16. Thread thread=new Thread(m1);
    17. thread.start();
    18. thread.interrupt();
    19. }
    20. }

    6、等待一个线程 

    有时,我们需要等待一个线程完成它的工作后,才能进行自己的下一步工作。例如,张三只有等李四转 账成功,才决定是否存钱,这时我们需要一个方法明确等待线程的结束。

    线程对象.join()

    在某个线程中调用别的线对象的join方法,意思就是这个线程要等待另外一个线程执行完毕再继续执行本线程的后序代码

    1. /**
    2. * 线程等待
    3. */
    4. public class ThreadJoin {
    5. public static void main(String[] args) throws InterruptedException {
    6. Thread t1=new Thread(()->{
    7. System.out.println(Thread.currentThread().getName()+"正在学习javaSE的知识~~");
    8. try {
    9. Thread.sleep(1000);
    10. } catch (InterruptedException e) {
    11. throw new RuntimeException(e);
    12. }
    13. },"javaSE线程~~");
    14. Thread t2=new Thread(()->{
    15. System.out.println(Thread.currentThread().getName()+"正在学习数据结构的知识~~");
    16. try {
    17. Thread.sleep(1000);
    18. } catch (InterruptedException e) {
    19. throw new RuntimeException(e);
    20. }
    21. },"数据结构线程~~");
    22. System.out.println("先学习javaSE~~");
    23. t1.start();
    24. t1.join();
    25. //此时t1线程已经执行结束
    26. t2.start();
    27. t2.join();
    28. //此时t2线程已经执行结束
    29. System.out.println("开始学习javaweb的知识~~");
    30. }
    31. }

     7、获取正在执行的线程对象

    8、休眠当前线程 

     该方法在那个线程调用,就会休眠那个线程

    四、线程的所有状态 

    1、多线程的状态获取

    1. public class Thread_state {
    2. public static void main(String[] args) {
    3. for (Thread.State state: Thread.State.values()) {
    4. System.out.println(state);
    5. }
    6. }
    7. }

     2、线程状态和状态转移的意义

     3、线程的新建,运行和终止状态

    1. /**
    2. * 线程的新建,运行和终止状态
    3. */
    4. public class State {
    5. public static void main(String[] args) {
    6. //产生一个新的线程对象,该对象默认的状态就是新建状态(NEW)
    7. Thread t1=new Thread(()->{
    8. for (int i = 0; i <100000; i++) {
    9. }
    10. },"子线程");
    11. System.out.println(t1.getName()+":"+t1.getState());
    12. t1.start();
    13. while (t1.isAlive()){
    14. System.out.println(t1.getName()+":"+t1.getState());
    15. }
    16. System.out.println(t1.getName()+":"+t1.getState());
    17. }
    18. }

    新建线程对象就是new状态

    就绪和运行都是Runnable状态

    线程的run方法执行完毕,或者抛出异常不正常执行完毕都会进入终止状态 

    4、三种堵塞状态

    1. /**
    2. * 三种阻塞状态
    3. */
    4. public class BlockedState {
    5. public static void main(String[] args) {
    6. Object lock=new Object();
    7. Thread t1=new Thread(()->{
    8. synchronized (lock){
    9. while (true){
    10. try {
    11. //TIME_WAITING 等待时间到了自动唤醒
    12. Thread.sleep(1000);
    13. } catch (InterruptedException e) {
    14. throw new RuntimeException(e);
    15. }
    16. }
    17. }
    18. },"t1线程~~");
    19. t1.start();
    20. Thread t2=new Thread(()->{
    21. synchronized (lock){
    22. //BLOCKED 等待获取锁对象
    23. System.out.println("哈哈哈哈~~~");
    24. }
    25. },"t2线程~~");
    26. t2.start();
    27. }
    28. }

     此时线程就会卡住,因为t1线程一直在睡觉,此时可以通过jconsole命令查看

    超时等待,该线程需要等一段时间之后才恢复执行

    该线程等待别的线程释放资源

      此时的t1线程还在死等,但是t2线程输出了,t1的状态就是等着别的线程去唤醒

    1. /**
    2. * 三种阻塞状态
    3. */
    4. public class BlockedState2 {
    5. public static void main(String[] args) {
    6. Object lock=new Object();
    7. Thread t1=new Thread(()->{
    8. synchronized (lock){
    9. try {
    10. //TIME_WAITING 等待时间到了自动唤醒
    11. // Thread.sleep(1000);
    12. //线程等待
    13. lock.wait();
    14. System.out.println("t1被唤醒了~~~");
    15. } catch (InterruptedException e) {
    16. throw new RuntimeException(e);
    17. }
    18. }
    19. },"t1线程~~");
    20. t1.start();
    21. Thread t2=new Thread(()->{
    22. synchronized (lock){
    23. //BLOCKED 等待获取锁对象
    24. System.out.println("哈哈哈哈~~~");
    25. try {
    26. Thread.sleep(1000);
    27. } catch (InterruptedException e) {
    28. throw new RuntimeException(e);
    29. }
    30. //唤醒t1线程
    31. lock.notify();
    32. }
    33. },"t2线程~~");
    34. t2.start();
    35. }
    36. }

     5、从运行态转到就绪态(yield)

    1. /**
    2. * 从运行态转到就绪态
    3. */
    4. public class YieldTest {
    5. public static void main(String[] args) {
    6. Thread t1=new Thread(()->{
    7. while (true){
    8. System.out.println(Thread.currentThread().getName());
    9. }
    10. },"线程一~~");
    11. t1.start();
    12. Thread t2=new Thread(()->{
    13. while (true){
    14. System.out.println(Thread.currentThread().getName());
    15. }
    16. },"线程二~~");
    17. t2.start();
    18. }
    19. }

    此时对于这两个线程来说,他们的输出数量是五五开的

    下面调用yield方法后,线程一就会让出CPU进入就绪态,等待被CPU继续调用

    1. /**
    2. * 从运行态转到就绪态
    3. */
    4. public class YieldTest {
    5. public static void main(String[] args) {
    6. Thread t1=new Thread(()->{
    7. while (true){
    8. System.out.println(Thread.currentThread().getName());
    9. //线程一就会让出CPU进入就绪态,等待被CPU继续调用
    10. Thread.yield();
    11. }
    12. },"线程一~~");
    13. t1.start();
    14. Thread t2=new Thread(()->{
    15. while (true){
    16. System.out.println(Thread.currentThread().getName());
    17. }
    18. },"线程二~~");
    19. t2.start();
    20. }
    21. }

    对于什么时候让出CPU,又是什么时候再次被CPU调用,这些都是由系统决定

    6、线程的重复启动引发的问题

    五、相关代码

    https://gitee.com/ren-xiaoxiong/rocket_class_-grammer/tree/master/src/Thread/https://gitee.com/ren-xiaoxiong/rocket_class_-grammer/tree/master/src/Thread/

  • 相关阅读:
    C现代方法(第5、6章)笔记
    Android 13 首个开发者预览版到来
    美国就业报告后美元小幅下跌 南非兰特走强
    【月度反思】202211
    ESP32 ESP-IDF TFT-LCD(ST7735 128x160) LVGL演示
    深入探索 PaddlePaddle 中的计算图
    虹科分享 | 确保冻干工艺开发中精确测量和数据完整性的5步指南
    我的两周年创作纪念日
    Github Actions 自动同步到 Gitee
    C++ 递归与面向对象编程基础
  • 原文地址:https://blog.csdn.net/m0_68989458/article/details/125338728