【多线程】初始线程和Thread类

news2025/3/25 22:30:29

一. 线程

1. 线程的引入

  • 虽然进程已经可以解决并发编程这种问题,但是进程在频繁进行创建和销毁的时候,系统开销非常大,如果一个服务器向你发送多个请求,针对每一个请求,都需要创建一个进程来应答,每个进程都需要申请资源(从硬盘加载到内存)和释放资源, 导致了资源的浪费
  • 服务器发送的多个请求,所需要的资源都是相同的,为了解决相同类型的问题而创建出多个进程,内存存放多个相同的资源,导致了内存资源的严重浪费

 线程的出现解决‘分配资源’和”释放资源“带来的额外开销,同时还支持并发处理,被称为轻量级进程

2. 线程的概念

  • 一个进程包含一个或者多个线程(线程是进程的更精细化分)
  • 一个线程包含一个PCB(一个进程包含多个PCB)
  • 一个进程里面的所有线程,共用一份资源

注意:多个线程的内存指针和文件描述符表都共用同一份(PCB中的内存指针都指向一个地址)

那么意味着创建第一个线程需要分配资源,后面的线程只需要使用第一个线程申请到的资源即可。

线程主要解决的问题:降低频繁申请资源和释放资源带来的开销(开销很小,并不是没有开销)

进程是资源分配的基本单位,线程是资源调度的基本单位

3. 线程和进程的区别

  1. 一个进程包含一个或者多个线程
  2. 每个线程都是单独的执行流,可以单独参与cpu的调度
  3. 一个进程分配一个资源,这个进程中的所有线程都共用这一个资源
  4. 进程是资源分配的基本单位,线程是资源调度的基本单位
  5. 进程和进程直接是相互独立的,彼此互不干扰
  6. 同一个进程中,线程和线程之间是相关的,一个线程抛出异常,可能会影响其他线程的工作(线程安全问题)
  7. 线程并不是越多越好,线程过少,资源利用率较低,线程过多,调度开销变大,要适中

 四. 线程的创建

(1)继承Thread类

class MyThread extends Thread{
    //run方法是线程的入口
    @Override
    public void run(){
        System.out.println("hello World");
    }
}

public class Demo_1 {
//    main方法是进程的入口
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
//        调用start方法会调用系统API,在系统内核中创建出线程
//        意味着可以同时具备多个执行流
    }
}

需要重写run方法

(2)实现Runnable接口

class MyThread3 implements Runnable{

    @Override
    public void run() {
        while(true){
            System.out.println("111");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
public class Demo_3 {
    public static void main(String[] args) {
        MyThread3 myThread3 = new MyThread3();
        Thread t = new Thread(myThread3);
//        Thread t = new Thread(new MyThread3());
        t.start();
        while(true){
            System.out.println("222");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

这种写法灵活性更强,让线程和要执行的任务进行解耦合,如果那个线程想要实现这个操作,直接调用这个类即可

(3)使用匿名内部类

public class Demo_4 {
    public static void main(String[] args) {
        Thread thread = new Thread(){
            @Override
            public void run() {
                while(true){
                    System.out.println("111");
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        };
        thread.start();

        while(true){
            System.out.println("222");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

没有具体的实例,不知道子类的名称 

(4)采用匿名内部类创建Runnable类

public class Demo_5 {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("111");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        thread.start();

        while(true){
            System.out.println("222");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

    }
}

(5)采用lambda(推荐写法)

public class Demo_6 {
    public static void main(String[] args) {
        Thread t = new Thread( ()-> {//()内是形参列表
            while(true){
                System.out.println("111");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        t.start();

        while(true){
            System.out.println("222");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

这里的()->括号内是形参类型

二. Thread类

在 Java 中,main函数是程序的入口点。当 Java 虚拟机(JVM)启动时,会创建一个主线程来执行main方法。

    public static void main(String[] args) {
        System.out.println("hello");
    }
  • 一个进程中至少包含一个线程,第一个线程被称为主线程

(1)初始Thread类

在 Java 中,Thread 类是用于创建和管理线程的类,它位于java.lang 包下,所以在使用的时候不需要导入 ,我们可以通过这个类,来创建一个进程

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

        System.out.println("222");
    }

注意:

  • run方法是线程的入口,想要线程实现的功能要写入run方法中 
  • Thread t = new Thread();是创建出一个实例,但是内核中还没有PCB,无法参与调度
  • t.start()是启动线程,在内核中创建出PCB,参与调度
  • 每一个线程都是独立的执行流,这两个代码是并发执行的
  • 所有的进程地位都是平等的,进程的执行顺序是随机的(抢占式执行
  • 为什么先输出“222”,因为创建一个线程的开销很小,但并不是没有,会花费很少的一段时间,所有大概率会先输出主线程的内容

(2)常用构造方法 

方法说明
Thread()创建线程对象
Thread(Runnable target)使用 Runnable 对象创建线程对象
Thread(String name)创建线程对象,并命名
Thread(Runnable target, String name)使用 Runnable 对象创建线程对象,并命名
1. 创建线程对象
        Thread thread = new Thread();
2. 使用 Runnable 

创建一个新的线程对象,并指定一个实现了Runnable接口的任务。

class MyThread3 implements Runnable{
    @Override
    public void run() {
       System.out.println("111");
    }
}
public class Demo_2 {
    public static void main(String[] args) {
        MyThread3 myThread3 = new MyThread3();
        Thread t = new Thread(myThread3);
    }
}
3. 创建线程对象,并命名
        Thread thread1 = new Thread("线程1");

 注意:命名对线程不会起到影响,只是方便测试

4. 使用 Runnable 并命名

创建一个新的线程对象,并指定一个实现了Runnable接口的任务。

class MyThread3 implements Runnable{
    @Override
    public void run() {
       System.out.println("111");
    }
}
public class Demo_2 {
    public static void main(String[] args) {
        MyThread3 myThread3 = new MyThread3();
        Thread t = new Thread(myThread3,"线程001");
    }
}

(3)常用方法

属性获取方法
IDgetId()
名称getName()
状态getState()
优先级getPriority()
是否后台线程isDaemon()
是否存活isAlive()
是否被中断isInterrupted()
  •  ID:jvm自动分配身份标识符,确保唯一性
  • 名称:如果没有命名,会自己分配一个名称,用于区别
  • 状态:常见有阻塞状态,运行状态,就绪状态等
  • 优先级:可以设置优先级,对内核调度器的调度起到一些影响,主要还是随机调度
  • 后台程序:不会阻止线程的结束
  • 前台程序:会阻止线程的结束(一般我们创建的代码就是前台代码,会因为运行导致进程不能结束)
  • 存活:创建出一个实例,不是存活,只有start后(在内核中创建出PCB可以进行调度),才是存活,线程执行完毕,内核中PCB被释放,即使实例还存在,也不是存活
public class Demo_9 {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{

        });

        thread.start();

        System.out.println(thread.getId());
        System.out.println(thread.getName());
        System.out.println(thread.getState());
        System.out.println(thread.getPriority());
        System.out.println(thread.isDaemon());
        System.out.println(thread.isAlive());
        System.out.println(thread.isInterrupted());
    }
}

(4)启动线程 

使用start方法,就是启动线程,在内核中创建出PCB可以进行调度

对于同一个Thread对象来说,start只能调用一次

start方法和run方法区别

start方法会创建一个新线程,run方法不会创建新线程,只是定义线程任务。

(5)终止线程

让run方法结束,线程任务完成就会终止线程,依赖run方法的代码逻辑

1. 通过共享的标记位
//通过设置一个变量,手动控制
public class Demo_7 {
    //必须写外面,如果写在主线程里面,会发生报错,lambda只能接受不能被修改的值
    static boolean flt = false;
    //成员变量可以,变成了内部类访问外部类成员

    public static void main1(String[] args) {
        Thread t = new Thread(()->{
            while(!flt){
                System.out.println("这是一个正在运行的线程……");
            }
            System.out.println("线程运行结束……");
        });
        t.start();

//        主线程进行干扰
        flt = true;
        System.out.println("让线程结束……");
//        交换代码位置,可能会导致顺序错误
//        交换之后,可能会导致再次打印这是一个正在运行的线程

    }

}

注意:共享的标志位要写在外面,因为不同的线程在不同的栈帧中运行,导致生命周期不一样,如果主线程栈帧销毁了,flt 变量不存在了,但是其他的线程的栈帧还在,想使用flt变量,会发生代码异常

 通过改变flt的属性,来影响进程

2. 调用 interrupt() 方法
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            while (!Thread.currentThread().isInterrupted()) {//默认为false
                System.out.println("这是一个正在运行的线程……");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
//                    throw new RuntimeException(e);//报错并停止运行,后面代码不会执行
                    e.printStackTrace();//显示错误原因
                    break;//跳出循环,继续向下执行
                }
            }
            System.out.println("线程执行完毕");
        });

        thread.start();

        Thread.sleep(3000);

        thread.interrupt();
        System.out.println("让进程停下");


    }

 Thread.currentThread()获取当前的引用,isInterrupted()是判定标志位,interrupt()是修改标志位 

注意:这里会发生异常,如果进程在处于休眠状态,interrupt()会将sleep提前唤醒,会执行异常处理(1.报错并停止运行 2. 提示错误原因继续向下运行 3.什么也不做继续向下运行)

其实还有一种强制终止线程,不管线程的死活,直接干掉他,但是java中不支持,因为如果线程执行了一半,直接干掉他,会产生一些垃圾数据(加载一半的错误信息等) 

(6)等待线程

线程之间的执行顺序是完全随机的(随机调度,抢占式执行),我们也不清楚线程执行多长时间(如果知道执行时间,可以根据休眠,控制线程执行的顺序),但是我们可以使用join方法控制结束的顺序

方法说明
public void join()等待线程结束
public void join(long millis)等待线程结束,最多等 millis 毫秒
public void join(long millis, int nanos)同理,但可以更高精度

注意:

  • Join方法不是确定线程的执行顺序,而是确定线程的结束顺序
  • 通过阻塞操作,让两个进程的结束时间产生了先后顺序
  • 如果A线程中调用了Join方法,就是A线程进入阻塞状态
1. 死等
        thread.join();

 这种等待属于死等,只要这个进程不结束,就一直处于阻塞状态(死等)

2. 设置等待的时间
        thread.join(100);

一般不会使用死等,万一代码出现故障,就会导致后面的代码不能工作(死机) 

举例:使用多个进程并发进行一系列的计数

public class Demo_8 {
    public static int result = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            for (int i = 0; i < 100; i++) {
                result +=i;
            }
        });

        thread.start();

//        解决方法1:
        Thread.sleep(100);
//        弊端:如果不知道thread线程要执行多久,休眠0.1秒可能会少

//        解决方法2:
        thread.join();
//        等thread线程执行完了,才会执行主线程
        
        System.out.println("结果:"+result);
//        因为执行的顺序不确定
//        结果可能出现3种,1,结果为0,2.结果为中间数,3.结果为4950
    }
}
  • 休眠的方式,控制执行顺序(前提是知道执行时间,才能设置休眠时间)
  • 死等的方式,等thread线程执行完了,才可以执行主线程的打印操作

(7)获取线程引用

Thread.currentThread()

 这个方法返回当前线程对象的引用

如果是继承Thread,那么可以使用this拿到线程的实例

class MyThread extends Thread{
    //run方法是线程的入口
    @Override
    public void run(){
        System.out.println("hello World");
        System.out.println(this.getId());
    }
}

如果是实现接口或者lambda的方式,this没有用,因为在静态方法中,this和进程实例没有直接的关联,不能用于获取进程实例。

(8)休眠线程

方法说明
sleep(long millis)使当前线程暂停执行指定的毫秒数
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }

sleep方法:会让线程从就绪状态调度到阻塞状态,不参与调度,sleep结束,会从阻塞状态调度到就绪状态

三. 线程的状态

java中,线程的常见状态

  • NEW:线程的实例创建成功,但是没有调用start方法在内核中创建线程
  • TERMINATED:线程的实例还存在,但是线程已经执行完毕,内核中的PCB已销毁
  • RUNNABLE:就绪状态,说明这个线程正在cpu上执行,或者准备就绪随时可以在CPU上执行
  • WAITING:阻塞状态(Join或者wait),不带时间的阻塞(死等)
  • TIMED_WAITING:阻塞状态,带时间的阻塞
  • BLOCKED:由于锁竞争引起的阻塞
public class Demo_10 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            while(!Thread.currentThread().isInterrupted()){
                System.out.println("111");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    break;
                }
            }
        });

//        运行前
        System.out.println(thread.getState());

        thread.start();
//        运行后
        System.out.println(thread.getState());

//        休眠后
        Thread.sleep(3000);
        System.out.println(thread.getState());

//        结束后
        thread.interrupt();
        thread.join();
        System.out.println(thread.getState());
    }
}

在线程的运行中,主要状态是:NEW  --> RUNNABLE --> TERMINATED

会因为执行一些特殊的操作,进入阻塞状态,进入阻塞的方式不同,被唤醒的方式也可能不同。


点赞的宝子今晚自动触发「躺赢锦鲤」buff!

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

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

相关文章

WebLogic中间件常见漏洞

一、后台弱⼝令GetShell 1.环境搭建 cd vulhub-master/weblogic/weak_password docker-compose up -d 2.访问网站并登陆后台 /console/login/LoginForm.jsp 默认账号密码&#xff1a;weblogic/Oracle123 3.点击部署&#xff0c;点击安装&#xff…

[笔记.AI]多头自注意力机制(Multi-Head Attention)

多头自注意力是深度学习领域&#xff0c;特别是自然语言处理&#xff08;NLP&#xff09;和Transformer模型中的关键概念。其发展源于对序列数据中复杂依赖关系的建模需求&#xff0c;特别是在Transformer架构的背景下。 举例 比喻-读长篇文章 用一个简单的比喻来理解“多头注…

【基于ROS的A*算法实现路径规划】A* | ROS | 路径规划 | Python

### 记录一下使用Python实现ROS平台A*算法路径规划 ### 代码可自取 &#xff1a;Xz/little_projecthttps://gitee.com/Xz_zh/little_project.git 目录 一、思路分析 二、算法实现 三、路径规划实现 一、思路分析 要求使用A*算法实现路径规划&#xff0c;可以将该任务分为三…

keda基于postgresql伸缩dify-api服务

1 概述 dify-api使用postgresql来存储数据&#xff0c;在dify控制台每新建一个聊天机器的聊天框&#xff0c;就会在conversations表里新插入一条记录&#xff0c;并且不断地更新字段updated_at&#xff0c;示例如下&#xff1a; dify# select * from conversations limit 1; …

趣味极简品牌海报艺术贴纸设计圆润边缘无衬线粗体装饰字体 Chunko Bold - Sans Serif Font

Chunko Bold 是一种功能强大的显示字体&#xff0c;体现了大胆极简主义的原则 – 当代设计的主流趋势。这种自信的字体将粗犷的几何形状与现代的趣味性相结合&#xff0c;具有圆润的边缘和强烈的存在感&#xff0c;与当今的极简主义设计方法完美契合。无论是用于鲜明的构图还是…

指针,数组 易混题解析(一)

目录 一.相关知识点 1.数组名是什么&#xff1f; 两个例外&#xff1a; 2.strlen 3.sizeof 4. * ( ) 与 [ ] 的互换 二.一维数组 三.字符数组 1. 字符 &#xff08;1&#xff09;sizeof &#xff08;2&#xff09;strlen 2.字符串 &#xff08;1&#xff09;si…

2025前端面试题记录

vue项目目录的执行顺序是怎么样的&#xff1f; 1、package.json   在执行npm run dev时&#xff0c;会在当前目录寻找package.json文件&#xff0c;此文件包含了项目的名称版本、项目依赖等相关信息。 2、webpack.config.js(会被vue-cli脚手架隐藏) 3、vue.config.js   对…

复变函数摘记2

复变函数摘记2 3. 级数3.1 复数项级数3.2 复变幂级数3.3 泰勒级数3.4 洛朗级数 3. 级数 \quad 复数项级数的一般项 α n a n i b n \alpha_na_n\text{i}b_n αn​an​ibn​ 为复数&#xff0c;与高等数学中无穷级数的分析方式类似&#xff0c;也是通过和函数来研究级数的收敛…

高频面试题(含笔试高频算法整理)基本总结回顾67

干货分享&#xff0c;感谢您的阅读&#xff01; &#xff08;暂存篇---后续会删除&#xff0c;完整版和持续更新见高频面试题基本总结回顾&#xff08;含笔试高频算法整理&#xff09;&#xff09; 备注&#xff1a;引用请标注出处&#xff0c;同时存在的问题请在相关博客留言…

Kafka--常见问题

1.为什么要使用 Kafka&#xff0c;起到什么作用 Kafka是一个高吞吐量、分布式、基于发布订阅的消息系统&#xff0c;它主要用于处理实时数据流 Kafka 设计上支持高吞吐量的消息传输&#xff0c;每秒可以处理数百万条消息。它能够在处理大量并发请求时&#xff0c;保持低延迟和…

优选算法的睿智之林:前缀和专题(一)

专栏&#xff1a;算法的魔法世界 个人主页&#xff1a;手握风云 目录 一、前缀和 二、例题讲解 2.1. 一维前缀和 2.2. 二维前缀和 2.3. 寻找数组的中心下标 2.4. 除自身以外数组的乘积 一、前缀和 前缀和算法是一种用于处理数组或序列数据的算法&#xff0c;其核心思想是…

【清华大学】AIGC发展研究(3.0版)

目录 AIGC发展研究报告核心内容一、团队简介二、AI哲学三、国内外大模型四、生成式内容&#xff08;一&#xff09;文本生成&#xff08;二&#xff09;图像生成&#xff08;三&#xff09;音乐生成&#xff08;四&#xff09;视频生成 五、各行业应用六、未来展望 AIGC发展研究…

JavaSE1.0(基础语法之运算符)

算术运算符 基础运算之加 减 乘 除 取余&#xff08; - * / %&#xff09; 运算符之相加&#xff08; &#xff09; public static void main(String[] args) {System.out.println("Hello world!");int a 10;int b 20;int c a b;System.out.println(c);//…

蓝桥与力扣刷题(蓝桥 数的分解)

题目&#xff1a;把 2019分解成 3个各不相同的正整数之和&#xff0c;并且要求每个正整数都不包含数字 2 和 4&#xff0c;一共有多少种不同的分解方法&#xff1f; 注意交换 3 个整数的顺序被视为同一种方法&#xff0c;例如 1000100118和 1001100018 被视为同一种。 解题思…

Java IO 流:从字节到字符再到Java 装饰者模式(Decorator Pattern),解析与应用掌握数据流动的艺术

在 Java 编程中&#xff0c;IO&#xff08;输入输出&#xff09;流是处理数据输入输出的核心工具。无论是读取文件、网络通信&#xff0c;还是处理用户输入&#xff0c;IO 流都扮演着重要角色。本文将深入探讨 Java IO 流的核心概念、分类、经典代码实例及其应用场景&#xff0…

爬虫案例-爬取某站视频

文章目录 1、下载FFmpeg2、爬取代码3、效果图 1、下载FFmpeg FFmpeg是一套可以用来记录、转换数字音频、视频&#xff0c;并能将其转化为流的开源计算机程序。 点击下载: ffmpeg 安装并配置 FFmpeg 步骤&#xff1a; 1.下载 FFmpeg&#xff1a; 2.访问 FFmpeg 官网。 3.选择 Wi…

nacos-未经授权创建用户漏洞

1、修改配置文件 vim application.properties# 修改配置项 nacos.core.auth.enabledtrue nacos.core.auth.enable.userAgentAuthWhitefalse2、重启nacos systemctl restart nacos3、验证 打开nacos部署服务器输入命令 curl -XPOST -d “usernametest123&passwordtest!123…

C++:IO库

一、C IO库的架构 C标准库中的IO系统基于流&#xff08;Stream&#xff09;​的概念&#xff0c;分为三层结构&#xff1a; ​流对象​&#xff08;如cin, cout, fstream&#xff09;​流缓冲区​&#xff08;streambuf&#xff0c;负责底层数据处理&#xff09;​数据源/目的…

企业级前端架构设计与实战

一、架构设计核心原则 1.1 模块化分层架构 典型目录结构&#xff1a; src/├── assets/ # 静态资源├── components/ # 通用组件├── pages/ # 页面模块├── services/ # API服务层├── store/ # 全局状态管理├── uti…

从入门到精通【MySQL】 CRUD

文章目录 &#x1f4d5;1. Create 新增✏️1.1 单行数据全列插入✏️1.2 单行数据指定列插入✏️1.3 多行数据指定列插入 &#x1f4d5;2. Retrieve 检索✏️2.1 全列查询✏️2.2 指定列查询✏️2.3 查询字段为表达式✏️2.4 为查询结果指定别名✏️2.5 结果去重查询 &#x1f…