• java多线程之概念和3种创建方式(详解)


    前言必读

    读者手册(必读)_云边的快乐猫的博客-CSDN博客

    一、概念讲解

    1.进程和线程的概念

    进程:一个执行的应用程序

    线程:一个应用程序内的具体执行不同模块

    2.进程和进程之间的关系

    进程和进程内存独立不共享 

    3.线程和线程之间的关系 

    (1)Java中至少有两个线程并发,一个是垃圾回收线程,一个是执行main方法的主线程

    (2) 一个线程一个栈(每个栈之间独立执行,互不干扰,即多线程并发),栈内存独立堆内存和方法区内存共享

    (3)多线程的目的:提高程序的处理效率

    (4)单核CPU能做到多线程并发吗:不能(cpu切换处理很快,超过肉眼速度,给人能多线程的假象)

    4.并发和并行的概念

    并发:一个cpu去执行不同的线程任务(哪里有任务就去哪里执行)

    并行:许多个cpu一起去执行不同线程任务(对一个cpu说,好兄弟,我们一起来齐心协力干吧)

    5.线程对象的生命周期:

    (1)新建状态(new):

    程序使用new关键字创建了一个进程后,该线程就是新建状态,内存由JVM分配,并初始化成员变量的值。 

    (2)就绪状态(runnable):

    线程对象调用start方法后,该线程就变成了就绪状态 ,JVM会为其创建方法栈和程序计数器,等待调度运行。

    (3)运行状态(running)

    处于就绪的线程获得了cpu,开始执行run方法的线程

    (4)阻塞状态(blocked) 

    线程因为某种原因放弃了cpu的使用权,暂停运行,直到线程通过就绪状态再进入运行状态。阻塞的情况有三种。

    (4.1)等待阻塞(o.wait):等待队列。运行的线程执行o.wait方法,JVM会把该线程放入等待队列

    (4.2)同步阻塞(lock):锁池。运行的线程在获取对象的同步锁时,若该同步锁被其他线程占用,JVM会把该线程放入锁池中

    (4.3)其他阻塞(sleep/join):运行的线程执行sleep、join或者IO请求时,JVM会把该线程设为阻塞状态。当sleep、join等待线程超时或终止或者IO处理完毕时,该线程重新进入就绪状态。

    (5)线程死亡(dead)

    线程结束就是死亡状态。线程死亡分为三种

    (5.1)正常结束:run或call方法执行完成

    (5.2)异常结束:线程抛出一个未捕获的Exception或者Error

    (5.3)强制结束:通过调用stop方法来结束线程,容易导致线程进入死锁,不推荐使用

    二、创建线程的方式

    方式一:编写一个类,继承java.lang.Thread类 

    优缺点:

    优点:编码简单

    缺点:继承存在单继承的局限性,线程类继承Thread后,不能继承其他类,不利于扩展

    步骤:

    1.main方法外定义一个子线程继承Thread类。

    2.重写run方法。(线程具体要执行的事情写在run方法里面)

    3.new出一个Thread子线程对象-----要在main方法里面。(子线程要写在主线程方法之前)

    4.调用start方法启动线程(调用这个才代表多线程启动,调用run只是单线程)

    代码例子: 

    1. package bao;
    2. public class Test {
    3. public static void main(String[] args) {
    4. //4.创建Thread子线程的对象(要创建在主线程之前,要不然失去了多线程的意义)
    5. Thread t = new MyThread();
    6. //5.启动线程(执行的是run方法)
    7. t.start();
    8. //6.创建一个main方法里面的主线程进行比较
    9. for (int i = 0; i < 5; i++) {
    10. System.out.println("主线程执行输出:"+i);
    11. }
    12. }
    13. }
    14. //1.定义一个子线程继承Thread类
    15. class MyThread extends Thread{
    16. //2.重写run方法:在里面定义子线程要做什么 快捷键:run+回车键
    17. @Override
    18. public void run() {
    19. super.run();
    20. //3.子线程要做的事情 例子:要看执行循环多少次
    21. for (int i = 0; i < 5; i++) {
    22. System.out.println("子线程执行输出:"+i);
    23. }
    24. }
    25. }

    运行结果:

     子线程执行输出:0
    主线程执行输出:0
    子线程执行输出:1
    主线程执行输出:1
    子线程执行输出:2
    主线程执行输出:2
    子线程执行输出:3
    主线程执行输出:3
    子线程执行输出:4
    主线程执行输出:4

    方式二: 编写一个类,实现java.lang.Runnable接口

    优缺点:

    优点:线程任务类只是实现接口,还可以继续继承类和实现接口,可扩展性强,容易实现资源共享

    缺点:编程多一层对象包装,如果线程有执行结果是不能直接返回的

    步骤:

    1.main方法外定义一个子线程MyRunnable实现Runnable接口

    2.重写run方法。(线程具体要执行的事情写在run方法里面)

    3.new出一个MyRunnable对象(然后传给Thread对象)

    4.new出一个Thread子线程对象-----要在main方法里面。(子线程要写在主线程方法之前)

    5.调用start方法启动线程(调用这个才代表多线程启动,调用run只是单线程)

    ps:这个创建线程方法和第一个创建线程方法就第一步不一样。还有在new子对象出来之前要创建一个MyRunnable对象,把这个值传入子线程对象里面。其他的都一样步骤 

    代码例子: 

    1. package bao;
    2. public class Test {
    3. public static void main(String[] args) {
    4. //4.创建MyRunnable对象,并传给Thread子线程
    5. Runnable target = new MyRunnable();
    6. //5.创建Thread子线程对象
    7. Thread t = new Thread(target);
    8. //6.启动线程
    9. t.start();
    10. //7.创建一个main方法里面的主线程进行比较
    11. for (int i = 0; i < 5; i++) {
    12. System.out.println("主线程输出:"+i);
    13. }
    14. }
    15. }
    16. //1.定义一个线程类,实现Runnable接口
    17. class MyRunnable implements Runnable{
    18. //2.重写run方法。run方法里面定义线程要执行的代码。快捷键:run+回车键
    19. @Override
    20. public void run() {
    21. //3.定义线程要执行的任务
    22. for (int i = 0; i < 5; i++) {
    23. System.out.println("子线程输出:"+i);
    24. }
    25. }
    26. }

    运行结果:

    子线程输出:0
    子线程输出:1
    主线程输出:0
    子线程输出:2
    主线程输出:1
    子线程输出:3
    主线程输出:2
    子线程输出:4
    主线程输出:3
    主线程输出:4

     方式三: 编写一个类,实现Callable接口

     优缺点:

    优点: 在方式二的优点上,还能获取线程执行结果(上面两个就不能)

    缺点:编程麻烦

    步骤:

    1.定义一个MyCallable线程类,实现Callable接口

    2.重写Call方法(线程具体要执行的事情写在Call方法里面)

    3.创建MyCallable任务对象,值交给下一步

    4.创建FutureTask,值交给下一步

    5.创建Thread线程对象

    6.调用start方法启动线程

    7.get方法输出线程执行结果

    代码例子: 

    1. package bao;
    2. import java.util.concurrent.Callable;
    3. import java.util.concurrent.ExecutionException;
    4. import java.util.concurrent.FutureTask;
    5. public class Test {
    6. public static void main(String[] args) throws ExecutionException, InterruptedException {
    7. //4.创建MyCallable任务对象,值交给下一步
    8. Callable call = new MyCallable(100);
    9. //5.创建FutureTask,值交给下一步
    10. FutureTask f1 = new FutureTask<>(call);
    11. //6.创建Thread线程对象
    12. Thread t = new Thread(f1);
    13. //7.启动线程
    14. t.start();
    15. //输出结果
    16. System.out.println(f1.get());//遇到异常抛出或者捕捉处理就好了
    17. }
    18. }
    19. //1.定义一个MyCallable线程类,实现Callable接口
    20. class MyCallable implements Callable{ //填写String就返回类型结果
    21. //3.2写在这里方便阅读
    22. private int n;
    23. public MyCallable(int n) {
    24. this.n=n;
    25. }
    26. //2.重写Call方法,并在里面写子线程要执行的任务 快捷键:call+回车键
    27. @Override
    28. public String call() throws Exception {
    29. //return null; 需要返回值这个就要注释掉
    30. //3.1定义线程要执行的任务 例子为求和
    31. int sum = 0;
    32. for (int i = 0; i < n; i++) {
    33. sum += i;
    34. }
    35. return "子线程的执行结果为"+sum;
    36. }
    37. }

    运行结果:

    子线程的执行结果为4950 

    三、常用API(其实也不常用)

    1.给线程改名字,为了方便区分线程。 

     作用:为了更加方便看到是哪个线程在执行

    代码例子来自方式一的代码,拿来修改添加

    详解: 

    1.按照这个格式输入

    2.这个位置生成name的有参构造器

    3.输入线程的名字 

    运行结果:(输出的基数大了才能看到主子线程交叉运行,比如500)

    主线程执行:0
    主线程执行:1
    主线程执行:2
    主线程执行:3
    主线程执行:4
    子线程:0
    子线程:1
    子线程:2
    子线程:3
    子线程:4

    2.线程睡眠(延迟执行) 

    作用:延迟线程的执行结果 

    代码例子来自方式一的代码,拿来修改添加

    运行结果:

    子线程输出后,延迟了5秒后才执行主线程 

    旁白:掌握第一种创建方式,后面的那两种方式也差不多,就是改变接口和创建线程对象之前加了1层或者2层对象而已。还有重写的方法不一样,其他的都一样。根据不同的场景来用。常用的API知识点看看就好了。也不难 

  • 相关阅读:
    PowerBI-窗口函数-INDEX
    经典算法(查找与排序)
    算法训练营day17
    Sprin、SpringBoot、微服务的概念
    测试用例设计方法之:入门试招,等价边界初探
    【Rust日报】2022-10-17 谷歌宣布推出 Rust 开发的 KataOS 操作系统
    被PMP考试“折磨”出来的考试心得,值得你一览
    HK32F030MF4P6 TM1650驱动
    Vue的快速入门(01)
    AVL 树的初步认识与基本操作
  • 原文地址:https://blog.csdn.net/m0_52861000/article/details/126846250