当前位置: 首页 > news >正文

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

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

文章目录

  • 1 线程的相关概念
  • 2 线程的基本使用
    • 2.1 继承Thread类
      • 2.1.1 为什么使用start而不直接调用run?
      • 2.1.2 追进start源码
    • 2.2 实现Runnable接口
      • 2.2.1 实现Runnable底层机制浅析
    • 2.3 继承Thread类与实现Runnable接口的区别
  • 写在最后


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);
    }
}

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;
            }
        }
    }
}

在这里插入图片描述
在程序中,我们可以使用 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());
        }
    }

在这里插入图片描述

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

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

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

在这里插入图片描述
此时,由于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;
            }
        }
    }
}

在这里插入图片描述

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. 因为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接口的方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制。

写在最后

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

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

相关文章:

  • 8、学习 Java 中的方法(方法的定义、可变参数、参数的传递问题、方法重载、方法签名)通过官方教程
  • 数据库基本结论
  • Django-(3)
  • HyperLynx(十五)多板仿真
  • ElasticSearch(四):ES nested嵌套文档与父子文档处理
  • java 基于springboot员工实训项目管理系统
  • SaaS行业的六大安全问题
  • Geoserver+Cesium 发布带样式矢量数据
  • 【C语言】数据类型、存储类
  • 免关注阅读CSDN博客和复制代码(2022.9.1)
  • shell脚本(四)处理用户输入
  • 08 SpringMVC跨域请求
  • Mac下根目录和home目录的区别
  • 猿创征文|opencv对滤波的处理
  • 输入年月日判断是本年的第多少天
  • 网络传输文件的问题
  • DOM的那些事
  • input实现文字超出省略号功能
  • iOS仿今日头条、壁纸应用、筛选分类、三方微博、颜色填充等源码
  • ucore操作系统实验笔记 - 重新理解中断
  • Wamp集成环境 添加PHP的新版本
  • Webpack 4 学习01(基础配置)
  • 对象管理器(defineProperty)学习笔记
  • - 概述 - 《设计模式(极简c++版)》
  • 紧急通知:《观止-微软》请在经管柜购买!
  • 如何在 Tornado 中实现 Middleware
  • 使用agvtool更改app version/build
  • Linux权限管理(week1_day5)--技术流ken
  • 基于django的视频点播网站开发-step3-注册登录功能 ...
  • 如何用纯 CSS 创作一个菱形 loader 动画
  • ​ 全球云科技基础设施:亚马逊云科技的海外服务器网络如何演进
  • ​【C语言】长篇详解,字符系列篇3-----strstr,strtok,strerror字符串函数的使用【图文详解​】
  • ​卜东波研究员:高观点下的少儿计算思维
  • ​二进制运算符:(与运算)、|(或运算)、~(取反运算)、^(异或运算)、位移运算符​
  • ​批处理文件中的errorlevel用法
  • ${factoryList }后面有空格不影响
  • ( )的作用是将计算机中的信息传送给用户,计算机应用基础 吉大15春学期《计算机应用基础》在线作业二及答案...
  • (笔记)Kotlin——Android封装ViewBinding之二 优化
  • (附源码)spring boot校园拼车微信小程序 毕业设计 091617
  • (三) diretfbrc详解
  • (转)eclipse内存溢出设置 -Xms212m -Xmx804m -XX:PermSize=250M -XX:MaxPermSize=356m
  • (转)ObjectiveC 深浅拷贝学习
  • ***利用Ms05002溢出找“肉鸡
  • .gitattributes 文件
  • .gitignore文件---让git自动忽略指定文件
  • .NET CF命令行调试器MDbg入门(三) 进程控制
  • .NET Core SkiaSharp 替代 System.Drawing.Common 的一些用法
  • .NET 动态调用WebService + WSE + UsernameToken
  • .NET 简介:跨平台、开源、高性能的开发平台
  • .NetCore项目nginx发布
  • .Net高阶异常处理第二篇~~ dump进阶之MiniDumpWriter
  • .net快速开发框架源码分享
  • .net连接oracle数据库
  • .NET中使用Redis (二)
  • .pyc文件还原.py文件_Python什么情况下会生成pyc文件?