• 【JavaSE】多线程篇(一)线程的相关概念与线程的基本使用


    💁 个人主页:黄小黄的博客主页
    ❤️ 支持我:👍 点赞 🌷 收藏 🤘关注
    🎏 格言:All miracles start from sometime somewhere, make it right now.
    本文来自专栏:JavaSE从入门到精通
    在这里插入图片描述


    1 线程的相关概念

    • 程序(program): 指为完成特定任务、用某种语言编写的 一组指令的集合。
    • 进程: 进程是指 运行中的程序, 比如我们平时使用微信,就相当于启动了一个进程,操作系统就会为该进程分配内存空间。进程是程序的一次执行过程,或是正在运行的一个程序。是 一种动态过程,有自身的产生、存在和消亡的过程。
      在这里插入图片描述
    • 线程: 线程是由进程创建的,是进程的一个实体。一个进程可以拥有多个线程。 比如日常我们使用视频软件,就是打开了一个进程,而在该软件中同时下载多个视频,每个下载视频的任务就可以看成一个进程。
    • 单线程: 同一个时刻,只允许执行一个线程。
    • 多线程: 同一时刻,可以执行多个线程。
    • 并发: 同一时刻,多个任务交替进行, 造成一种“貌似同时”的错觉,简单的说,单核CPU实现的多任务就是并发。
    • 并行: 同一时刻,多个任务同时执行, 多核CPU可以实现并行。并发并行可以同时存在。

    🐱 下面的例子中获取了当前电脑cpu的核心数量:

    /**
     * @author 兴趣使然黄小黄
     * @version 1.0
     * 获取当前电脑的cpu核心数
     */
    public class ThreadTest01 {
        public static void main(String[] args) {
    
            Runtime runtime = Runtime.getRuntime();
            // 获取当前电脑cpu数量/核心
            int cpuNums = runtime.availableProcessors();
            System.out.println("当前电脑的cpu核心数:" + cpuNums);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    2 线程的基本使用

    2.1 继承Thread类

    第一种使用方式为,继承Thread类,并重写run方法

    1. 当一个类继承了Thread类,该类就可以当作线程使用;
    2. 重写run方法,在里面会写上自己的业务代码;
    3. Thread类实现了 Runnable接口 的run方法。

    在这里插入图片描述

    🦁 案例演示:

    (1)编写一个程序,开启一个线程,该线程每相隔1秒,在控制台输出:“我是大黄”;
    (2)进行改进,当输出10次时,结束该线程;
    (3)使用JConsole监控线程执行情况,并画出程序示意图。

    /**
     * @author 兴趣使然黄小黄
     * @version 1.0
     * 通过继承Thread使用线程
     */
    public class ThreadTest02 {
        public static void main(String[] args) {
            // 创建一个DaHuang对象,可以当作线程使用
            DaHuang daHuang = new DaHuang();
            daHuang.start();  // 启动线程
    
        }
    }
    
    class DaHuang extends Thread{
        /**
         次数
         */
        int times = 0;
        @Override
        public void run() {// 重写run方法,自己的业务逻辑
            while (true) {
                System.out.println("我是大黄" + (times + 1) + "次");
                times++;
                // 休眠1秒
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (times == 10){
                    break;
                }
            }
        }
    }
    
    • 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

    在这里插入图片描述
    在程序中,我们可以使用 Thread.currentThread().getName()来获取当前线程的名称。上述程序 执行过程中,会打开一个进程,进入main主线程。当执行start时,会执行Thread-0子线程。 示意图如下:
    在这里插入图片描述
    需要特别注意的是,Thread-0子线程的执行,并不会造成main主线程的阻塞! 修改主方法如下,结果可以看到,两个线程都在执行:

        public static void main(String[] args) {
            // 创建一个DaHuang对象,可以当作线程使用
            DaHuang daHuang = new DaHuang();
            daHuang.start();  // 启动线程
            
            for (int i = 0; i < 10; i++) {
                System.out.println("i = " + i + ", 线程" + Thread.currentThread().getName());
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述

    2.1.1 为什么使用start而不直接调用run?

    上述代码中,使用start后,会调用run。但是,如果通过直接使用run方法,则是由主线程调用的!代码如下:

        public static void main(String[] args) {
            // 创建一个DaHuang对象,可以当作线程使用
            DaHuang daHuang = new DaHuang();
            daHuang.run();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述
    此时,由于run是主线程调用的,那么,就不是真正意义的多线程。run仅仅是一个普通的方法,没有真正的启动一个线程。 在主线程中,必须要当前run这条语句的动作执行完毕,才能继续向后执行,造成了阻塞。

    2.1.2 追进start源码

    1. start启动后,会进入到start方法,该方法最核心的,是执行了start0方法:
      在这里插入图片描述
    2. start0是本地方法,是由jvm调用的,底层是c/c++实现的,真正实现多线程效果的,是start0方法:
      在这里插入图片描述
    3. start方法调用start0方法后,该线程并不一定会立马执行,只是将线程变成了可运行状态。 具体什么时候执行,取决于CPU,由CPU统一调度。
      在这里插入图片描述

    2.2 实现Runnable接口

    由于java是单继承的,在某些情况下,一个类可能已经继承了某个父类,这时再用继承Thread类的方法来创建线程显然是不可能的了。
    因此,java设计者,提供了另一种方法,就是通过实现Runnable接口来创建线程
    🦁 案例演示:

    请编写程序,该程序可以每隔1秒,在控制台输出 “你好!”,当输出5次后,自动退出。请通过实现Runnable接口的方式来实现。

    /**
     * @author 兴趣使然黄小黄
     * @version 1.0
     * 通过实现Runnable接口的方式创建线程
     */
    public class ThreadTest03 {
    
        public static void main(String[] args) {
            Say say = new Say();
            // 创建Thread对象,把say对象(实现了Runnable),放入Thread
            Thread thread = new Thread(say);
            thread.start();
        }
    }
    
    class Say implements Runnable{
        int times = 0;
    
        @Override
        public void run() {
            while (true){
                System.out.println("你好!" + (++times));
    
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
                if (times == 5){
                    break;
                }
            }
        }
    }
    
    • 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

    在这里插入图片描述

    2.2.1 实现Runnable底层机制浅析

    使用实现Runnable的方式创建线程,底层使用了设计模式——代理模式。

    🐘 简单模拟:

    /**
     * @author 兴趣使然黄小黄
     * @version 1.0
     */
    public class ThreadTest03 {
    
        public static void main(String[] args) {
            Dog dog = new Dog();
            //dog实现了Runnable接口
            ThreadProxy threadProxy = new ThreadProxy(dog);
            threadProxy.start();
        }
    }
    
    /**
     * 线程代理类
     */
    class ThreadProxy implements Runnable{
    
        /**
         * 属性,类型为 Runnable
         */
        private  Runnable target = null;
    
        @Override
        public void run() {
            if (target != null){
                target.run();//动态绑定(运行类型)
            }
        }
        
        public ThreadProxy(Runnable target){
            this.target = target;
        }
        
        public void start(){
            start0();
        }
        
        public void start0(){
            run();
        }
    }
    
    class Animal{}
    
    class Dog extends Animal implements Runnable{
        @Override
        public void run() {
            System.out.println("Dog 汪汪汪");
        }
    }
    
    • 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

    在这里插入图片描述

    🐱 说明:

    1. 因为Dog类实现了Runnable接口,所以dog对象可以传入ThreadProxy的构造器;
    2. 此时,调用start会调用ThreadProxy的run方法;
    3. 由于动态绑定机制,运行类型的dog,最终会调用dog的run方法。

    2.3 继承Thread类与实现Runnable接口的区别

    1. 从java的设计上看,通过继承Thread类与实现Runnable接口的方式来创建线程本质上没有区别,从jdk文档中,我们可以知道,Thread类本身就实现了Runnable接口;
    2. 实现Runnable接口的方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制。

    写在最后

    🌟以上便是本文的全部内容啦,后续内容将会持续 免费更新,如果文章对你有所帮助,麻烦动动小手点个赞 + 关注,非常感谢 ❤️ ❤️ ❤️ !
    如果有问题,欢迎私信或者评论区!
    在这里插入图片描述

    共勉:“你间歇性的努力和蒙混过日子,都是对之前努力的清零。”
    在这里插入图片描述

  • 相关阅读:
    基于html+css+javascript+jquery制作北京景点介绍7页 WEB静态旅游景点区主题网页设计与制作
    Vue3 中的 v-bind 指令:你不知道的那些工作原理
    Metabase学习教程:模型-2
    Dubbo后台管理和监控中心部署
    「网页开发|前端开发|Vue」09 Vue状态管理Vuex:让页面根据用户登录状态渲染不同内容
    【安卓13-Framework】SystemUI定制之屏蔽下拉状态栏部分快捷按钮
    C++ if 语句
    项目构建生命周期与插件
    利用Anaconda 在Pycharm安装模组比如serial@问题No module named ‘serial.tools‘
    微信小程序:超强大微信小程序源码下载内含几十款功能王者战力查询,游戏扫码登录,王者巅峰信息查询等等支持流量主收益和CPS收益
  • 原文地址:https://blog.csdn.net/m0_60353039/article/details/126611277