【JavaEE】浅谈线程(一)

news2024/11/18 22:39:44

线程

  • 前言
  • 线程的由来
  • 线程是什么
    • 线程的属性
    • 线程更高效的原因
    • 举个例子(线程便利性的体现)
  • 多线程代码
    • 线程并发执行的代码
    • 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");
    }
}

总结

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

.NET8 和 Vue.js 的前后端分离

在.NET 8中实现前后端分离主要涉及到两个部分:后端API的开发和前端应用的开发。后端API通常使用ASP.NET Core来构建,而前端应用则可以使用任何前端框架或技术栈,比如Vue.js、React或Angular等。下面是一个简化的步骤指南,帮助你在…

指针 基础知识

本笔记为观看56 指针-指针的定义和使用_哔哩哔哩_bilibili后的学习笔记 指针的定义和使用 1、定义指针 int main () {//1、定义指针int a 10;//指针定义的语法: 数据类型 * 指针变量名;int * p;//让指针记录变量a的地址p &a; //& 为取址符cou…

Mac资源库的东西可以删除吗?mac资源库在哪里打开 cleanmymacx是什么 cleanmymac免费下载

在使用Mac电脑的过程中,用户可能会遇到存储空间不足的问题。一种解决方法是清理不必要的文件,其中资源库(Library)文件夹是一个常被提及但又让人迷惑的目标。Mac资源库的东西可以删除吗?本文旨在解释Mac资源库的作用、…

Java常用函数接口

Java常用函数接口 Java 8 中引入的常用函数式接口,也就是 java.util.function 包中的接口。这些接口提供了一种简洁的方式来定义函数,常用于 Lambda 表达式和方法引用。下面是一些常用的接口: 一、Predicate(断言) …

应用性能分析工具CPU Profiler

简介 本文档介绍应用性能分析工具CPU Profiler的使用方法,该工具为开发者提供性能采样分析手段,可在不插桩情况下获取调用栈上各层函数的执行时间,并展示在时间轴上。 开发者可通过该工具查看TS/JS代码及NAPI代码执行过程中的时序及耗时情况…

c语言之动态内存管理及常见错误分析,柔性数组,内存划分

目录 前言 一:malloc,calloc,realloc,free四大函数 1.malloc 2.free 3.calloc 4.realloc 二:常见错误分析 1.malloc返回值不检查直接使用 2.对动态开辟空间的越界访问 3.对非动态开辟空间free 4.使用free释放动态开辟内存的一部分 5.对…

QAuth 2.0

OAuth 2.0授权框架支持第三方支持访问有限的HTTP服务,通过在资源所有者和HTTP服务之间进行一个批准交互来代表资源者去访问这些资源,或者通过允许第三方应用程序以自己的名义获取访问权限。 为了方便理解,可以想象OAuth2.0就是在用户资源和第…

多路转接-epoll/Reactor(2)

epoll 上次说到了poll,它存在效率问题,因此出现了改进的poll----epoll。 目前epoll是公认的效率最高的多路转接的方案。 快速了解epoll接口 epoll_create: 这个参数其实已经被废弃了。 这个值只要大于0就可以了。 这是用来创建一个epoll模…

用友U9 存在PatchFile.asmx接口任意文件上传漏洞

声明: 本文仅用于技术交流,请勿用于非法用途 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。 简介 用友U9是由中国用友软件股份有限公司开发的一款企业…

FJSP:狐猴优化算法(Lemurs Optimizer,LO)求解柔性作业车间调度问题(FJSP),提供MATLAB代码

一、柔性作业车间调度问题 柔性作业车间调度问题(Flexible Job Shop Scheduling Problem,FJSP),是一种经典的组合优化问题。在FJSP问题中,有多个作业需要在多个机器上进行加工,每个作业由一系列工序组成&a…

Linux(Ubuntu)中创建【samba】服务,用于和Windows系统之间共享文件

目录 1.先介绍一下什么是Samba 2.安装,配置服务 安装 配置(smb.conf) 配置用户 3.出现的问题(Failed to add entry for user XXXX) 4.创建文件夹 5.windows访问 1.先介绍一下什么是Samba Samba是一个开源的软…

HTML5.Canvas简介

1. Canvas.getContext getContext(“2d”)是Canvas元素的方法,用于获取一个用于绘制2D图形的绘图上下文对象。在给定的代码中,首先通过getElementById方法获取id为"myCanvas"的Canvas元素,然后使用getContext(“2d”)方法获取该Ca…

剑指Offer题目笔记26(动态规划的基础知识)

面试题88: 问题: ​ 一个数组cost的所有数字都是正数,它的第i个数字表示在一个楼梯的第i级台阶往上爬的成本,在支付了成本cost[i]之后可以从第i级台阶往上爬1级或2级。请计算爬上该楼梯的最少成本。 解决方案一:&…

【简单讲解下epoll】

🎥博主:程序员不想YY啊 💫CSDN优质创作者,CSDN实力新星,CSDN博客专家 🤗点赞🎈收藏⭐再看💫养成习惯 ✨希望本文对您有所裨益,如有不足之处,欢迎在评论区提出…

Day:004(1) | Python爬虫:高效数据抓取的编程技术(数据解析)

数据解析-正则表达式 在前面我们已经搞定了怎样获取页面的内容,不过还差一步,这么多杂乱的代码夹杂文字我们怎样 把它提取出来整理呢?下面就开始介绍一个十分强大的工具,正则表达式! 正则表达式是对字符串操作的一种…

力扣Lc28---- 557. 反转字符串中的单词 III(java版)-2024年4月06日

1.题目描述 2.知识点 1)用StringBuilder的方法 实现可变字符串结果 最后返回的时候用.toString的方法 2)在Java中使用StringBuilder的toString()方法时,它会返回StringBuilder对象当前包含的所有字符序列的字符串表示。 在我们的例子中,sb是一个Stri…

Django之五种中间件定义类型—process_request、process_view、process_response.......

目录 1. 前言 2. 基础中间件 3. 如何自定义中间件 4. 五种自定义中间件类型 4.1 process_request 4.2 process_view 4.3 process_response 4.4 process_exception 4.5 process_template_response 5. 最后 1. 前言 哈喽,大家好,我是小K,今天咋们…

90天玩转Python-02-基础知识篇:初识Python与PyCharm

90天玩转Python系列文章目录 90天玩转Python—01—基础知识篇:C站最全Python标准库总结 90天玩转Python--02--基础知识篇:初识Python与PyCharm 90天玩转Python—03—基础知识篇:Python和PyCharm(语言特点、学习方法、工具安装) 90天玩转Python—04—基础知识篇:Pytho…

ubuntu20.04.6将虚拟机用户目录映射为磁盘Z

文章目录 linux虚拟机设置为NAT模式安装sshd服务映射目录到windows磁盘安装samba套件修改配置文件smb.conf重启smbd并设置用户名和密码 windows映射遇到的问题1、设置好之后映射不成功2、smbd下载失败3、smbd密码配置问题4、当有改动时候,最好重启一下smbd服务 linu…

图解大型网站多级缓存的分层架构

前言 缓存技术存在于应用场景的方方面面。从浏览器请求,到反向代理服务器,从进程内缓存到分布式缓存,其中缓存策略算法也是层出不穷。 假设一个网站,需要提高性能,缓存可以放在浏览器,可以放在反向代理服…