• 【JavaEE初阶】线程的概念与创建



    本节目标

    • 认识多线程
    • 创建多线程

    🌲线程的概念

    🚩线程是什么

    Java 给多线程编程提供了内置的支持。 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

    一个线程就是一个 “执行流”. 每个线程之间都可以按照顺讯执行自己的代码. 多个线程之间 “同时” 执行着多份代码。

    举个例子:

    一家公司要去银行办理业务,既要进行财务转账,又要进行福利发放,还得进行缴社保。
    如果只有张三一个会计就会忙不过来,耗费的时间特别长。为了让业务更快的办理好,张三又找来两位同事李四、王五一起来帮助他,三个人分别负责一个事情,分别申请一个号码进行排队,自此就有了三个执行流共同完成任务,但本质上他们都是为了办理一家公司的业务。
    此时,我们就把这种情况称为多线程,将一个大任务分解成不同小任务,交给不同执行流就分别排队执行。其中李四、王五都是张三叫来的,所以张三一般被称为主线程(Main Thread)

    🚩为啥要有线程

    首先, “并发编程” 成为 “刚需”.

    • 单核 CPU 的发展遇到了瓶颈. 要想提高算力, 就需要多核 CPU. 而并发编程能更充分利用多核 CPU
      资源.
    • 有些任务场景需要 “等待 IO”, 为了让等待 IO 的时间能够去做一些其他的工作, 也需要用到并发编
      程.

    其次, 虽然多进程也能实现 并发编程, 但是线程比进程更轻量.

    • 创建线程比创建进程更快.
    • 销毁线程比销毁进程更快.
    • 调度线程比调度进程更快.

    最后, 线程虽然比进程轻量, 但是人们还不满足, 于是又有了 “线程池”(ThreadPool) 和 “协程”(Coroutine)

    关于线程池和协程博主会在后面一一介绍.此处暂时不做过多讨论.

    🚩进程和线程的区别

    • 进程是包含线程的. 每个进程至少有一个线程存在,即主线程
    • 进程和进程之间不共享内存空间. 同一个进程的线程之间共享同一个内存空间.

    比如之前的多进程例子中,每个客户来银行办理各自的业务,但他们之间的票据肯定是不想让别人知道的,否则钱不就被其他人取走了么。
    而上面我们的公司业务中,张三、李四、王五虽然是不同的执行流,但因为办理的都是一家公司的业务,所以票据是共享着的。这个就是多线程和多 进程的最大区别。

    • 进程是系统分配资源的最小单位,线程是系统调度的最小单位
      在这里插入图片描述

    🚩Java 的线程 和 操作系统线程 的关系

    线程是操作系统中的概念. 操作系统内核实现了线程这样的机制, 并且对用户层提供了一些 API 供用户使用(例如 Linux 的 pthread 库).

    Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行了进一步的抽象和封装

    😎第一个多线程程序

    感受多线程程序和普通程序的区别:

    • 每个线程都是一个独立的执行流
    • 多个线程之间是 “并发” 执行的

    程序代码如下:

    class MyThread extends Thread {
        @Override
        public void run() {
            while(true) {
                System.out.println("hello thread");
                //休眠一秒
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
    public class TestMian_1 {
        public static void main(String[] args) {
            Thread t1 = new MyThread();
            t1.start();
    
            while (true) {
                System.out.println("hello main");
                //休眠一秒
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    以上就是一个简单的多线程的程序

    start:真正的创建一个线程(从系统里创建)
    MyThread里面的run:描述线程需要干啥

    如果按照我们以前的思维,因为我们写的是个死循环,所以应该执行一个语句,但是这是多线程,执行结果如下:
    在这里插入图片描述
    结果显示交替进行,这就展现出了多线程与普通程序的区别

    🚩使用 jconsole 命令观察线程

    我们可以使用jdk自带的工具 jconsole查看当前Java进程中所有的线程

    操作流程如下:

    1. 第一步,找到jdk
      在这里插入图片描述
    2. 第二步,点进去,找到里面的bin文件点进去
      在这里插入图片描述
    3. 第三步,点击bin文件夹里面的jconsole
      在这里插入图片描述
    4. 第四步,找到你所创建进程在这里插入图片描述
    5. 第五步,遇到以下情况,直接点击不安全连接就好
      在这里插入图片描述
    6. 第六步,点击线程进行查看
      在这里插入图片描述

    🎍创建线程

    🚩方法一:继承 Thread 类

    1. 继承 Thread 来创建一个线程类
    class MyThread extends Thread {
    	@Override
    	public void run() {
    		System.out.println("这里是线程运行的代码");
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 创建 MyThread 类的实例
    MyThread t = new MyThread();
    
    • 1
    1. 调用 start 方法启动线程
    t.start(); // 线程开始运行
    
    • 1

    🚩方法2:实现 Runnable 接口

    1. 实现 Runnable 接口
    class MyRunnable implements Runnable {
    	@Override
    	public void run() {
    		System.out.println("这里是线程运行的代码");
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 创建 Thread 类实例, 调用 Thread 的构造方法时将 Runnable 对象作为 target 参数
    Thread t = new Thread(new MyRunnable());
    
    • 1
    1. 调用 start 方法
    t.start(); // 线程开始运行
    
    • 1

    🚩方法1、2对比

    对比上面两种方法:

    • 继承 Thread 类, 直接使用 this 就表示当前线程对象的引用.

    • 实现 Runnable 接口, this 表示的是 MyRunnable 的引用. 需要使用Thread.currentThread()

    🚩方法3:其他变形

    • 匿名内部类创建 Thread 子类对象
    // 使用匿名类创建 Thread 子类对象
    Thread t1 = new Thread() {
    	@Override
    	public void run() {
    		System.out.println("使用匿名类创建 Thread 子类对象");
    	}
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 匿名内部类创建 Runnable 子类对象
    // 使用匿名类创建 Runnable 子类对象
    Thread t2 = new Thread(new Runnable() {
    	@Override
    	public void run() {
    		System.out.println("使用匿名类创建 Runnable 子类对象");
    	}
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • lambda 表达式创建 Runnable 子类对象
    // 使用 lambda 表达式创建 Runnable 子类对象
    Thread t3 = new Thread(() -> System.out.println("使用匿名类创建 Thread 子类对象"));
    Thread t4 = new Thread(() -> {
    System.out.println("使用匿名类创建 Thread 子类对象");
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5

    🎋多线程的优势

    增加运行速度

    可以观察多线程在一些场合下是可以提高程序的整体运行效率的。

    • 使用 System.nanoTime() 可以记录当前系统的 纳秒 级时间戳.

    • serial 串行的完成一系列运算. concurrency 使用两个线程并行的完成同样的运算

    public class ThreadAdvantage {
        // 多线程并不一定就能提高速度,可以观察,count 不同,实际的运行效果也是不同的
        private static final long count = 10_0000_0000;
        public static void main(String[] args) throws InterruptedException {
            // 使用并发方式
            concurrency();
            // 使用串行方式
            serial();
        }
        private static void concurrency() throws InterruptedException {
            long begin = System.nanoTime();
            // 利用一个线程计算 a 的值
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    int a = 0;
                    for (long i = 0; i < count; i++) {
                        a--;
                    }
                }
            });
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    int b = 0;
                    for (long i = 0; i < count; i++) {
                        b--;
                    }
                }
            });
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
            // 统计耗时
            long end = System.nanoTime();
            double ms = (end - begin) * 1.0 / 1000 / 1000;
            System.out.printf("并发: %f 毫秒%n", ms);
        }
        private static void serial() {
            // 全部在主线程内计算 a、b 的值
            long begin = System.nanoTime();
            int a = 0;
            for (long i = 0; i < count; i++) {
                a--;
            }
            int b = 0;
            for (long i = 0; i < count; i++) {
                b--;
            }
            long end = System.nanoTime();
            double ms = (end - begin) * 1.0 / 1000 / 1000;
            System.out.printf("串行: %f 毫秒%n", ms);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    运行结果如下:
    在这里插入图片描述

    ⭕总结

    关于《【JavaEE初阶】线程的概念与创建》就讲解到这儿,感谢大家的支持,欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下!

  • 相关阅读:
    ai_drive42_如何兼容一致性和互补性?多模态融合基础问题及算法解析
    Springboot使用@Cacheable注解实现数据缓存
    阿里云一面:并发场景下的底层细节 - 伪共享问题
    查找组成一个偶数最接近的两个素数
    未在本地计算机上注册“Microsoft.ACE.OLEDB.12.0”提供程序
    【SQL刷题】Day11----SQL通配符专项练习
    【python数据分析基础】—对列操作:调整DataFrame的列顺序
    Mysql-体系结构
    【PAT甲级】1060 Are They Equal
    文件下载的其他方法
  • 原文地址:https://blog.csdn.net/m0_71731682/article/details/133644051