线程与多线程编程

1. 线程 

1.1 概念

线程又可以称为轻量级进程 ,在进程的基础上做出了改进。

一个进程在刚刚启动时,做的第一件事就是申请内存和资源,进程需要把依赖的代码和数据,从磁盘加载到内存中这件事是比较耗费时间的,有的业务场景可能会频繁的创建,销毁进程,也就导致了大量的开销。而线程则省去了分配资源和释放资源带来的开销。

1.2 线程与进程的区别 

与进程相同,线程也可以用PCB描述,所拥有的属性也是大致相同的。不同点在于,每个线程都有自己独立的系统资源,而多个线程的系统资源是可以相互共享的。所以这些线程之间需要的重复的系统同资源就只需要申请一次避免了重复的开销

 举个例子:1线程需要资源A,B  2线程需要资源B,C  3线程需要资源 A,C。现在1线程执行了,2线程执行时就只需要申请资源 C, 再到3线程执行时则不需要再申请资源了。

也不是所有线程都可以共享资源,系统会把能资源共享的线程分成组,就称为线程组,而线程组也是进程的一部分,也就是一个进程是由多个线程组成的 

 注意:

  1. 进程是包含线程的
  2. 每个线程也是一个独立的执行流,可以执行一些代码并单独参与到cpu的调度中(状态,上下文,优先级,记账信息,每个线程都有自己的一份
  3. 每个进程有自己的资源,进程中的线程共用这一份资源(内存空间和文件描述符表)
  4. 进程是资源分配的基本单位,线程是调度执行的基本单位
  5. 同一个进程中的线程之间,可能会互相干扰引起线程安全问题
  6. 进程和进程之间不会互相影响,如果同一个进程中的线程,抛出异常,可能会影响到其他线程,把整个进程中的所有线程都终止

 2. 多线程编程

写代码的时候,可以使用多进程进行并发编程,也可以使用多线程并发编程。

在Java中是不太推荐多进程编程的,

2.1 创建线程的方法

2.1.1 继承 Thread  

1.  创建Thread子类 重写run方法

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("调用了MyThread类中的run方法");
    }
}

 run方法是该线程的入口,不需要手动调用,jvm会在线程创建好时自动调用

2. 创建子类的实例

Thread t = new MyThread();

3. 调用 Thread类中的start方法

 t.start();

注意:

  • 调用Thread类中的start方法,才会真正调用系统api在内核中创建出线程,直接调用run方法是不会创建出线程的
  • 一个Thread对象只能调用一次start方法

上面我们说了,每个线程都是一个独立的执行流,就相当于,我们创建的线程中的run方法和main方法是同时执行的

例如以下代码:

class MyThread2 extends Thread {
    @Override
    public void run() {
        while(true) {
            System.out.println("MyThread中的run方法");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
public class ThreadDemo2 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new MyThread2();
        t.start();
        while(true) {
            System.out.println("main方法");
            Thread.sleep(1000);
        }
    }
}

输出结果:

如果把

t.start();

改为

t.run();

 则不会打印 “main方法” 因为 t并没有创建出线程 ,run方法是在main方法的线程中执行

2.1.2 实现 Runnable接口 

实现Runnable 接口重写 run方法

class MyThread3 implements Runnable {
    @Override
    public void run() {
        System.out.println("MyThread3中的run方法");
    }
}

 创建一个 MyThread3 对象作为参数传入 Thread的构造方法然后调用start

  public static void main(String[] args) {
        Thread t = new Thread(new MyThread3());
        t.start();
    }
2.1.3 使用匿名内部类
public class ThreadDemo4 {
    public static void main(String[] args) {
        Thread t = new Thread() {
            @Override
            public void run() {
                System.out.println("MyThread4中的run方法");
            }
        };
        t.start();
    }
}
public class ThreadDemo5 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("MyThread5中的run方法");
            }
        });
        t.start();
    }
}
2.1.4 使用lambda表达式
public class ThreadDemo6 {
    public static void main(String[] args) {
        Thread t = new Thread(()-> {
            System.out.println("MyThread6中的run方法");
        } );
        t.start();
    }
}

 推荐使用这种写法。

2.2 Thread类的属性和方法

2.2.1 构造方法
  1. Thread() 无参构造方法,创建一个新的线程对象。
  2. Thread(Runnable target)  使用指定的Runnable对象作为线程的目标,创建一个新的线程对象。
  3. Thread(String name) 使用指定的名称创建一个新的线程对象。
  4. Thread(Runnable target, String name) 使用指定的Runnable对象和名称创建一个新的线程对象。
  5. Thread(ThreadGroup group, Runnable target) 使用指定的线程组和Runnable对象创建一个新的线程对象。
  6. Thread(ThreadGroup group, Runnable target, String name) 使用指定的线程组、Runnable对象和名称创建一个新的线程对象。

 解释:每个线程都有一个名称,如果没有给它命名则会默认为Thread-0,Thread-1......累加。当一个线程运行时我们可以通过jdk的工具 jconsole 查看线程的状态,名称等。这个工具在jdk目录底下的bin文件夹中

示例代码:

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

运行该代码然后打开jconsole

如图所示:

我们在本地进程即可看到我们运行的ThreadDemo7,点击连接即可查看线程的状态等信息

线程的名字是可以重复的

2.2.2 常见的属性

解释: 

  1. id:每个线程都有一个唯一的id,用于标识线程。

  2. 名称:线程可以设置一个可选的名称来标识自己。可以通过setName(String name)方法设置线程的名称。

  3. 状态:线程在运行过程中会处于不同的状态。(下面会详细讲解)

  4. 优先级:每个线程都有一个优先级,用于指示线程在竞争CPU资源时的相对重要性。优先级范围从1到10,默认为5。可以通过setPriority(int priority)方法设置线程的优先级。

  5. 是否后台进程:线程可以设置为后台进程。后台进程不会阻止程序的终止,当所有前台线程结束时,后台线程也会自动结束。可以通过setDaemon(boolean on)方法将线程设置为后台进程

  6. 是否存活:线程是否仍然存活(即尚未终止)。

  7. 是否被中断:线程可以通过调用interrupt()方法中断自己或其他线程 以及通过interrupted()静态方法检查当前线程是否被中断,并清除中断状态。

  线程中断注意事项:

示例代码:

public class ThreadDemo8 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            //currentThread() 方法是获取当前线程的实例,即这里的t
            while(!Thread.currentThread().isInterrupted()) {
                System.out.println("ThreadDemo8");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("执行完毕");
        });
        t.start();
        Thread.sleep(5000);
        System.out.println("让线程结束");
        t.interrupt();
    }
}

当我们运行代码结果如下:

我们可以看到,代码并不像我们预期的那样结束,而是在继续运行 ,这是因为 sleep的原因,因为,当我们运行interrupt()时,sleep()可能还未结束,于是sleep就被提前唤醒了

sleep被提前唤醒会做两件事:

1. 抛出InterruptedException异常

2.将Thread对象的isInterrupted标志位设置为false

所以运行完interrupt()后标志位已经被设为true但是sleep又把它改回false了,所以会继续执行。我们要结局这个问题只需在catch中加一个break

2.2.3 join方法

如果我们希望某个线程在另一个线程之前执行完,可以用到join方法

示例代码:

public class ThreadDemo10 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            for(int i = 0; i < 5; i++) {
                System.out.println("ThreadDemo10");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();
        t.join();//让main线程等待t线程执行完
        System.out.println("希望t线程执行完再执行这条语句");
    }
}

 运行结果:

执行join后,如果t线程在运行中,main线程就会阻塞(主动放弃去cpu上执行 )直到t运行结束

除了无参数的join方法还有带参数的join方法

 解释:

  • join():死等,一定会等到调用该方法的线程执行完
  • join(long millis):带超时时间的等待,如果在设定的时间内,调用该方法的线程没有执行完,则不会继续等待
  • join(long millis, int nanos):带超时时间的等待,精确到纳秒,如果在设定的时间内,调用该方法的线程没有执行完,则不会继续等待

示例代码: 

public class ThreadDemo11 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            for(int i = 0; i < 5; i++) {
                System.out.println("ThreadDemo11");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();
        //让join只等2秒,如果没等到就不等了
        t.join(2000);
        System.out.println("main");
    }
}

运行结果:

 interrupt() 方法可以把阻塞 等待的join提前唤醒

 

 2.3 线程的状态

Java中有以下线程状态:

  1. NEW:线程被创建但还未开始执行。
  2. RUNNABLE:线程正在执行或准备开始执行。(就绪状态)
  3. BLOCKED:锁竞争引起的阻塞。(线程安全会详讲)
  4. WAITING:线程正在等待另一个线程的特定操作完成。(不带时间的死等,join()或wait()会进入这个状态
  5. TIMED_WAITING:线程正在等待另一个线程的特定操作完成,但设置了最大等待时间。(使用sleep()方法或者带超时时间的join()方法会进入这个状态)
  6. TERMINATED:线程已经执行完毕结束。

示例代码:

public class ThreadDemo12 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            for(int i = 0; i < 5; i++) {
                System.out.println("ThreadDemo12");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        //线程被创建但还没开始执行
        System.out.println(t.getState());

        t.start();
        //线程在执行中
        System.out.println(t.getState());

        t.join();
        //线程执行完毕
        System.out.println(t.getState());
    }
}

 执行结果:

 

我们也可以通过 jdk的jconsole工具查看线程的状态 

 

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

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

相关文章

简单测试大语言模型 Yi-34B 的中日英能力

简单测试大语言模型 Yi-34B 的中日英能力 0. 背景1. 中文测试2. 日文测试3. 英文测试 0. 背景 简单测试一下C-Eval 排行榜第一&#xff08;20231129时点&#xff09;的 Yi-34B 的中日英能力&#xff0c; 1. 中文测试 问题1&#xff0c;回答正确。 问题2&#xff0c;回答正确。…

【教3妹学编程-算法题】拼车

3妹&#xff1a;“太阳当空照&#xff0c;花儿对我笑&#xff0c;小鸟说早早早&#xff0c;你为什么背上炸药包” 2哥 :3妹&#xff0c;什么事呀这么开发。 3妹&#xff1a;2哥你看今天的天气多好啊&#xff0c;阳光明媚、万里无云、秋高气爽&#xff0c;适合秋游。 2哥&#x…

SOCKET、TCP、HTTP之间的区别与联系

SOCKET、TCP、HTTP之间的区别与联系 一、 Socket 1、什么是socket2、为什么需要socket3、建立socket连接 二、HTTP(基于TCP) 1、HTTP的概念2、HTTP连接的特点 连接请求&#xff1a;一次连接连接请求&#xff1a;短连接(socket是长连接) 三、TCP/IP协议簇 四、HTTP、Socket…

Linux:查看端口占用的进程

命令 netstat -tunlp可以从图中看到&#xff0c;端口被那个进程占用&#xff0c;对应进程的pid是多少。

哪个软件有消除笔?这三款消除笔轻松消除杂物

想必大家都有遇到日常下载保存的图片中有多余元素想要去除的情况&#xff0c;奈何不会用PS&#xff0c;导致无法快速解决消除图片水印的问题&#xff0c;这时你需要消除笔来帮你一键消除&#xff0c;那么你想知道哪个软件有消除笔&#xff1f;今天来分享几款好用的消除笔软件&a…

jquery 判断是手机端还是电脑端

判断为手机端&#xff1a; var sUserAgent navigator.userAgent.toLowerCase(); var bIsIpad sUserAgent.match(/ipad/i) "ipad"; var bIsIphoneOs sUserAgent.match(/iphone os/i) "iphone os"; var bIsMidp sUserAgent.match(/midp/i) "mid…

Sass 同时导出JavaScript 和 CSS变量

Sass 官网 安装插件 注意 sass-loader 版本没设太高&#xff0c;否则会报错 Syntax Error: TypeError: this.getOptions is not a function npm i sass sass-loader10 -D创建 Sass 文件 variables.module.scss。注意这里是 module.scss&#xff1a; 否则报错 Cant find st…

产品学习之路(一)

在做好开发的同时&#xff0c;还需要熟悉产品业务逻辑&#xff0c;不能为了功能而做功能&#xff0c;要从产品经理的角度去看待每个需求和客户痛点所在&#xff0c;这样针对产品设计出来的东西自己也有发言权&#xff1b; 目前作为一名前端开发人员&#xff0c;也在自学产品知识…

【中文编码】利用bert-base-chinese中的Tokenizer实现中文编码嵌入

最近接触文本处理&#xff0c;查询了一些资料&#xff0c;记录一下中文文本编码的处理方法吧。   先下载模型和词表&#xff1a;bert-base-chinese镜像下载   如下图示&#xff0c;下载好的以下文件均存放在 bert-base-chinese 文件夹下    1. 词编码嵌入简介 按我通俗的…

匿名结构体类型、结构体的自引用、结构体的内存对齐以及结构体传参

文章目录 &#x1f680;前言&#x1f680;结构体✈️结构体类型的声明✈️结构体变量的创建与初始化✈️结构体类型的特殊声明✈️结构体的自引用✈️结构体的内存对齐&#x1f681;修改默认对齐数 ✈️结构体传参 &#x1f680;前言 在C语言中有着各种数据类型&#xff0c;这…

第九节HarmonyOS 常用基础组件3-TextInput

一、TextInput描述 TextInput组件用于输入单行文本&#xff0c;响应输入事件。TextInput的使用也非常广泛&#xff0c;例如应用登录账号密码、发送消息等。和Text组件一样&#xff0c;TextInput组件也支持文本样式设置&#xff0c;下面的示例代码实现了一个简单的输入框&#x…

JavaScript WebAPI(三)(详解)

这次介绍一下webAPI中的一些知识&#xff1a; 回调函数 回调函数是指 如果将函数A做为参数传递给函数B时&#xff0c;我们称函数A为回调函数 例如&#xff1a; // 立即执行函数中传递的函数是一个回调函数 (function(){ console.log("我是回调函数") })(); // …

<Linux>(极简关键、省时省力)《Linux操作系统原理分析之linux存储管理(1)》(17)

《Linux操作系统原理分析之linux存储管理&#xff08;1&#xff09;》&#xff08;17&#xff09; 6 Linux 存储管理6.1 80x86 的分段机制6.1.1 80x86 的虚拟存储空间6.1.2 段描述符表6.1.3 逻辑地址向线形地址的转换 6 Linux 存储管理 6.1 80x86 的分段机制 Linux 最早在 in…

Docker篇之利用docker搭建ftp服务器可实现多用户上传

一、前言 场景&#xff1a;公司需要搭建FTP服务器&#xff0c;供内网之前可以互相传递数据&#xff0c;安全稳定&#xff0c;需要满足开通多个账号&#xff0c;每个用户上传的文件有自己对应的文件目录。 这里建议&#xff1a;用户目录Disk尽量大一点&#xff0c;避免因为空间不…

三十六、seata的部署和集成

seata的部署和集成 一、部署Seata的tc-server 1.下载 首先我们要下载seata-server包&#xff0c;地址在http&#x1f615;/seata.io/zh-cn/blog/download.html 当然&#xff0c;资料也准备好了&#xff1a; 2.解压 在非中文目录解压缩这个zip包&#xff0c;其目录结构如下…

走向未来能源之巅:可控核聚变的探索与挑战

走向未来能源之巅:可控核聚变的探索与挑战 引言 随着人类文明的进步和科技的发展,对能源的需求与日俱增。传统的化石燃料能源面临着枯竭和环境问题的双重压力,因此,寻找一种清洁、可持续、高效的能源成为了全球科学家的共同使命。在这个过程中,可控核聚变作为一种具有巨…

synchronized和volatile的区别是什么?

synchronized和volatile是Java中的两个关键词&#xff0c;分别用于实现线程同步和线程间的可见性。 synchronized用于实现线程之间的互斥同步&#xff0c;即同一时刻只能有一个线程访问被synchronized修饰的代码块或方法&#xff0c;其他线程需要等待。synchronized确保了线程…

大学里学编程,为什么这么难?

在大学学习计算机专业&#xff0c;为何很多同学觉得编程学得不顺心呢&#xff1f;许多同学会有这种感觉&#xff0c;在上大学里的计算机专业课程时&#xff0c;听得头都大了&#xff0c;但是真正要写代码&#xff0c;却不知道从哪里开始&#xff0c;或是觉得&#xff0c;大学里…

【golang】为什么使用goland终端修改不了Go语言的配置环境?

问题 最近在做项目时&#xff0c;需要使用golang的交叉编译&#xff0c;在windows系统上打包一个可以在linux系统上运行的golang程序的二进制文件。 这就需要暂时修改一下golang的配置环境&#xff1a; set GOARCH amd64 set GOOS linux但是修改的时候发现在goland终端输入…

基于51单片机的交通灯_可调时间_夜间+紧急模式

51单片机交通灯 1 讲解视频&#xff1a;2 功能要求3 仿真图&#xff1a;4 原理图PCB5 实物图6 程序设计&#xff1a;7 设计报告8 资料清单&#xff08;提供资料清单所有文件&#xff09;&#xff1a;设计资料下载链接&#xff1a; 51单片机简易交通灯_可调时间_夜间紧急 仿真代…