【JavaEE】浅谈线程(一)

线程

  • 前言
  • 线程的由来
  • 线程是什么
    • 线程的属性
    • 线程更高效的原因
    • 举个例子(线程便利性的体现)
  • 多线程代码
    • 线程并发执行的代码
    • jconsole(观测多线程)
  • 线程的调度问题
  • 创建线程的几种方法
    • 1)通过继承Thread 重写run
    • 2)使用Runnable接口 重写run 有利于解耦合
    • 3)匿名内部类
    • 4)Runnable匿名内部类
    • 5)Lambda表达式
  • 总结


前言

线程的由来

现如今的CPU存在多个核心,因此能够同时处理多个进程,我们也称为并发执行(并行+并发)。通过这样的“多进程模式”,能够很好的利用多核CPU,极大的提升了效率。
但是,多进程的编程虽然好用,但也会带来一些问题。
在服务器中,需要给多个客户端提供服务,如果同时来了多个客户端,如此一来,只能用多个进程处理这件事情。这样会导致服务器频繁进行创建+销毁进程的操作,这是极其影响效率的。
于是引入了"轻量级进程"——线程。线程的创建和开销会小的多。

线程是什么

线程,可以理解为“线程的一部分”;一个进程中可以包含多个线程。
在进程中存在PCB结构体,一个PCB代表着一个线程,而一个进程由多个PCB构成,若干个PCB联合在一起,描述了一个进程。举个例子来说:进程像是一个小区,而线程就是一个个住在小区里的人。

线程的属性

因为线程存在与进程中,因此他们共用一个PCB,为了区分线程之间的区别,设置了一个新的概念词tgid(tgd)。同一个进程的tgid是相同的而pid是不同的;线程们共用同一份内存指针和文件描述符表。
因为线程们共用内存资源和文件资源的原因,线程之间可以访问到彼此的数据。而每个线程都是在独立的CPU上调度执行。因此我们可以认为:线程是系统调度执行的基本单位,进程是系统资源分配的基本单位。

线程更高效的原因

进程在创建和销毁过程中需要进行资源分配和资源释放的过程,而线程存在于进程中,线程之间共用资源,在线程创建和销毁的过程中可以省略资源分配/释放的步骤,省去了申请资源的过程,因此线程是更高效,更轻量级的一种进程。

举个例子(线程便利性的体现)

现在有一项任务需要完成,我们可以选择创建一个进程来解决,如果这个任务太繁重了,我们可以选择创建多个进程来分担压力。这种方法好是好,但是进程的创建和销毁会占用到资源,造成资源的浪费。
但是现在,我们学过线程了!我们可以选择在进程中创建多个线程,同时通过多核CPU的特点让线程可以达到并发执行的效果,这样既不会有申请资源的烦恼,又能高速高效的执行任务,可谓是一举多得!(鼓掌)
当然说归说,我们不能想到这么方便,直接在进程中创建多个线程叠罗汉不就好了,这肯定是不对的。俗话说的好:物极必反。如果我们一味的引入,自然会引发其他的问题(且听下回分解)

多线程代码

在JVM中已经对线程封装好了,即Thread类。我们可以通过Thread类实现线程的创建。接下来我写一份简单的代码进行演示

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("hello thread");
    }
}
public class ThreadDemo1{
    public static void main(String[] args) {
        Thread t = new MyThread();
        t.start();
    }
}

如何理解这段代码呢?首先创建了MyThread类并继承Thread方法,实现动态绑定。接着重写Thread的run方法;run()方法中包含的是线程t要执行的方法;而t.start()方法是启动线程t的方法。这段代码中有两个线程:t线程和main线程。由此我们可以知道,在学习线程之前的代码中,只存在着一个线程:main线程。同时我们也称main线程为主线程。
当然,run()是start()创建出来的线程,在线程里被调用的,run()方法已经创建好而不去手动调用,将这个方法交给系统/其他的库调用,即“回调函数”。

线程并发执行的代码

或许在上面的代码中我们不能很好的看出来t线程和main线程是怎么体现并发执行的,因此这边我分别在两个线程中加入两个循环以求更好的体现出线程并发执行的特点。

class MyThread extends Thread {
    @Override
    public void run() {
        while (true){
            System.out.println("hello thread");
        }
        
    }
}
public class ThreadDemo1{
    public static void main(String[] args) {
        Thread t = new MyThread();
        t.start();
        while (true){
            System.out.println("hello main");
        }
    }
}

在这里插入图片描述
在这里我们也看到了 “hello thread” 和 "hello main"线程在控制台上交替打印,这也代表着两个线程正在并发执行。

jconsole(观测多线程)

jconsole是java的一款观测线程的实用工具,我已在其他文章中编写好,需要的可以观看下面的文章。
jconsole的简单使用

线程的调度问题

在上面的线程循环代码中我们已经可以证明多线程是并发执行的。
那么他们执行的顺序是否存在规律呢?
我们可以使用Thread.sleep()方法进行简单的观测。

Thread.sleep()可以设置时间使线程停止接下来的工作,使线程进入休眠状态(阻塞)。在规定的时间过后线程继续运行,执行未完成的任务。

代码如下

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 ThreadDemo1{
    public static void main(String[] args) {
        Thread t = new MyThread();
        t.start();
        while (true){
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

部分运行结果如下:
在这里插入图片描述
从这部分运行结果我们可以看出来,线程的运行使无序的。在操作系统中也称之为**“抢占式执行”。任何一个线程,在执行到任何一个代码的过程中,都可能被其他的线程抢占掉它的cpu资源,于是cpu就给别的线程执行了。
如果是这样的结果,显然这样的抢占式执行充满了随机性,执行效果难以预测甚至
可能引入bug。**这肯定是程序猿们不想看到的,这涉及到了线程的安全问题。

创建线程的几种方法

1)通过继承Thread 重写run

在上面的代码中已经展示,这边不再赘述。

2)使用Runnable接口 重写run 有利于解耦合

在Runnable接口中,我们可以通过查看源码看到,其中只有一个抽象类方法run(),因此Runnable的作用就是描述一个任务,一个行为。**无论是线程还是其他方法执行run()这个方法都是可以的**。

通过Runnable方法实现的多线程有一个好处:解耦合
在第一种写法中,线程重写自己的run()方法,以及线程调用的都是自己本身的方法;而使用Runnable方法之后,在后续进行代码的改动将会降低成本。

在这里插入图片描述
下面给出一个举例

class MyRunnable implements Runnable{
    @Override
    public void run() {
        while (true){
            System.out.println("hello runnable");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

    }
}
public class ThreadDemo2{
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable());
        t.start();
        while (true){
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

3)匿名内部类

继承Thread 重写run,创建Thread的子类(不知名),同时也创建了一个不具名子类实例。我们看看下面的代码:

public class ThreadDemo3{
    public static void main(String[] args) {
        Thread t1 = new Thread(){
            @Override
            public void run() {
                System.out.println("使用匿名内部类");
            }
        };
        t1.start();
    }
}

4)Runnable匿名内部类

与Thread匿名内部类的写法类似,在new Runnable之后直接重写Runnable的方法,这也创建了一个实例(不知名)实现了Runnable接口的方法。
演示代码如下:

public class ThreadDemo4 {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("匿名内部类使用Runnable");
            }
        });
        t1.start();
        System.out.println("hello main");
    }
}

5)Lambda表达式

通过使用lambda表达式代替需要重写的run方法。

public class ThreadDemo5 {
    //lambda表达式
    //lambda本质上也是一个匿名函数,用完就丢
    public static void main(String[] args) {
        Thread t = new Thread(() ->{
            while (true){
                System.out.println("hello thread");
            }

        });
        t.start();
        System.out.println("hello main");
    }
}

总结

线程是一个轻量级进程,能够很大程度上节约成本和资源,在开发中有着十分重要的作用。同样的,引入多线程意味着我们需要解决一些引入所带来的问题。(如线程安全问题)这是随着我们逐渐深入了解所需要做的。
需要源码的可以点击链接:线程代码
有帮助到大家的请三连~感谢!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/521785.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Ubuntu系统同时使用AMD和NVIDIA GPU出现的问题及解决

问题产生: Ubuntu 22.04系统同时使用了AMD W7900和NVIDIA GTX 1070Ti,想用1070Ti做显示,W7900做运算。结果Ubuntu 22.04系统不能启动了。 解决方法: 同时按下Ctrl和Alt键,并保持按住。在此过程中,按一下…

Windows Server 2012 R2安装远程桌面服务

文章目录 一、打开【服务器管理器】二、点击【添加角色和功能】三、点击【下一步】四、点击【下一步】五、点击【下一步】![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/05b61a830faf477e81f858ec00bbdfff.png)六、勾选【远程桌面服务】→点击【下一步】七、点击【…

ruoyi-nbcio-plus基于vue3的flowable流程设计器组件的升级修改

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码: https://gitee.com/nbacheng/ruoyi-nbcio 演示地址:RuoYi-Nbcio后台管理系统 http://122.227.135.243:9666/ 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码&#xff1a…

删除有序链表中的重复元素

. - 力扣(LeetCode) 给定一个已排序的链表的头 head , 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表 。 示例 1: 输入:head [1,2,3,3,4,4,5] 输出:[1,2,5]示例 2&…

软考系统规划与管理师-第1章考点思维导图

系规|教程第1章脑图发布,用4幅图掌控信息系统综合知识的考点地图 2024年指尖疯在9年之后,首次扩展到系规课程。 虽然目前系统规划与管理师的教程是否改版存在不确定性,但是不影响咱们先概要了解当前的教程,使用思维导图…

Python实现 AI 绘图(非常详细)零基础入门到精通,收藏这一篇就够了

今天给大家带来了 Python 对接阿里大模型,通过 AI 实现文本生成图片。 相关资料 这个功能使用的主要 API 是阿里的大模型服务。 开通服务 阿里文档 https://help.aliyun.com/zh/dashscope/developer-reference/quick-start-1?disableWebsiteRedirecttrue 获取 a…

阿里云4核8G服务器ECS通用算力型u1实例优惠价格

阿里云4核8G服务器优惠价格955元一年,配置为ECS通用算力型u1实例(ecs.u1-c1m2.xlarge)4核8G配置、1M到3M带宽可选、ESSD Entry系统盘20G到40G可选,CPU采用Intel(R) Xeon(R) Platinum处理器,阿里云活动链接 aliyunfuwuq…

DFS:floodfill算法解决矩阵联通块问题

floodfill,翻译为洪水灌溉,而floodfill算法本质上是为了解决在矩阵中性质相同的联通块问题。 一、图像渲染 . - 力扣(LeetCode) class Solution { public:int dx[4]{0,0,1,-1};int dy[4]{1,-1,0,0};int prev;//记住初始值int m,…

内容检索(2024.04.07)

随着创作数量的增加,博客文章所涉及的内容越来越庞杂,为了更为方便地阅读,后续更新发布的文章将陆续在此汇总并附上原文链接,感兴趣的小伙伴们可持续关注文章发布动态! 本期更新内容: 1. 真实案例分享--P…

能不能换DB吗?--抽象工厂模式

1.1 就不能不换DB吗? 都是换数据库惹的祸。 "我们团队前段时间用.net的C#来开发好一个项目,是给一家企业做的电子商务网站,是用SQL Server作为数据库的,应该说上线后除了开始有些小问题,基本都还可以。而后&#…

政安晨:【Keras机器学习实践要点】(十八)—— 利用视觉转换器进行图像分类

目录 简介 设置 准备数据 配置超参数 使用数据增强 实施多层感知器(MLP) 将创建修补程序作为一个层 实施补丁编码层 建立 ViT 模型 编译、培训和评估模式 政安晨的个人主页:政安晨 欢迎 👍点赞✍评论⭐收藏 收录专栏: T…

Android源码笔记-输入事件(二)

这一节主要了解输入事件的获取,InputReaderThread继承自C的Thread类,Thread类封装了pthread线程工具,提供了与Java层Thread类相似的API。C的Thread类提供了一个名为threadLoop()的纯虚函数,当线程开始运行后,将会在内建…

【Linux实践室】Linux高级用户管理实战指南:创建与删除用户组操作详解

🌈个人主页:聆风吟_ 🔥系列专栏:Linux实践室、网络奇遇记 🔖少年有梦不应止于心动,更要付诸行动。 文章目录 一. ⛳️任务描述二. ⛳️相关知识2.1 🔔Linux创建用户组命令2.1.1 知识点讲解2.1.2…

Arduino的OTA在线升级

一、OTA 介绍 OTA是Over-the-Air的缩写,中文意思是空中下载技术。通过移动通信(GSM或CDMA)的空中接口对SIM卡数据及应用进行远程管理的技术。空中接口可以采用WAP、GPRS、CDMA1X及短消息技术。OTA技术的应用,使得移动通信不仅可以…

读所罗门的密码笔记12_群雄逐鹿(上)

1. 国际电信规则 1.1. 美国坚持互联网自由和极少的内容限制,这一立场肯定会遭到许多国家的反对 1.2. 除去两个各方针锋相对、无法妥协的议题,比如内容限制规定,实际上所有国家都已在打击垃圾邮件和常见网络安全威胁方…

Windows Server 2012 R2安装Web服务器IIS

文章目录 一、打开【服务器管理器】二、点击【添加角色和功能】三、点击【下一步】四、点击【下一步】五、点击【下一步】六、勾选【Web服务器(IIS)】→点击【添加功能】→点击【下一步】七、勾选【.NET Framework 3.5 功能】→点击【下一步】八、点击【下一步】九、点击【下一…

基于H2O AutoML与集成学习策略的房屋售价预测模型研究与实现

项目简述: 本项目采用H2O AutoML工具,针对加州房屋销售价格预测问题进行了深入研究与建模。项目以Kaggle提供的加州房屋 交易数据集为基础,通过数据清洗、特征工程、模型训练与评估等步骤,构建了一种基于集成学习策略的房价预测模…

LeetCode刷题记(二):31~60题

31. 下一个排列 整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。 例如,arr [1,2,3] ,以下这些都可以视作 arr 的排列:[1,2,3]、[1,3,2]、[3,1,2]、[2,3,1] 。 整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。…

如何确认RID池是否耗尽,以及手动增加RID池大小

确认RID池是否耗尽: 事件查看器: 在RID主控域控制器上打开事件查看器,导航至“Windows日志 > 应用程序和服务日志 > Microsoft > Windows > Directory Service > Operations”。搜索事件ID 16656和16657。事件ID 16656表明RID…

C++超高精度计时器

#include <chrono> #include <QDebug> #pragma execution_character_set("utf-8") /*1 秒&#xff08;s&#xff09; 1,000 毫秒&#xff08;ms&#xff09; 1,000,000 微秒&#xff08;μs&#xff09; 1,000,000,000 纳秒&#xff08;ns&#xff09…