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

【多线程入门篇】 创建线程以及线程的属性

在这里插入图片描述

大家好呀
我是浪前

今天给大家讲解的是创建线程以及线程的属性

祝愿所有点赞关注的人,身体健康,一夜暴富,升职加薪迎娶白富美!!!
点我领取迎娶白富美大礼包

🍓多线程编程:

前言:
我们为什么不用多进程?

多进程相关的API在Java标准库中没有提供
Java适合使用多线程来进行编程:

多线程在并发编程的时候,效率更高
尤其对于Java进程是要启动Java虚拟机来说
启动虚拟机这个事情开销很大, 搞多个Java进程就是搞多个Java虚拟机

于是java使用了标准库把与多线程编程有关API给封装了

比如在Java中的Thread类

一个进程中至少有一个线程
这个进程中的第一个线程就叫做主线程

main方法就是主线程的入口方法

每个线程都是一个独立的执行流,相互独立执行,互不干扰

线程的执行顺序是不确定的,是随机的

为什么这里是随机的呢?
因为操作系统中有一个调度器模块,这个模块的实现方式就是类似于一种随机调度的效果

🍓随机调度:

一个线程什么时候被调度到CPU上执行,时机是不确定的
一个线程什么时候从CPU上下来,给别的线程让位, 时机也是不确定的

随机调度这种也叫抢占式执行:
这个特性会导致多线程安全问题

Windows等等主流的操作系统都是抢占式执行

🍓观看线程的详细信息:

使用一个程序来观看线程的详细信息
在jdk中的bin目录之下有一个jconsole.exe的程序
在这里插入图片描述

在这里插入图片描述

在使用这个jconsole.exe程序查看线程的详细信息之前要先确保两个点:

  1. 确保你的程序(线程)已经先跑起来了
  2. 有些需要使用管理员方式来运行

如图所示:
在这里插入图片描述

在这里插入图片描述

🍓sleep

线程中的while循环转得太快了, 使用sleep方法来休眠,使得循环转得慢一些,
sleep是Thread的静态方法, 属于Thread类

我们可以在线程中加入sleep 来降低循环速度

在这里插入图片描述
时间单位换算:
1s = 1000ms
1 ms = 1000us
1us = 1000ns

🍓创建线程

线程的创建有好几种方式:

  1. 继承Thread类, 重写run方法
  2. 实现Runnable接口
  3. 还是继承Thread,重写run,但是使用匿名内部类
  4. 还是实现那Runnable, 重写run, 也是使用匿名内部类
  5. 基于Lambda表达式)最推荐的方式

🍓第一种: 继承Thread类, 重写run方法:

class MyThread extends Thread{  @Override  public void run(){  while(true){  System.out.println("hello thread");  try {  Thread.sleep(1000);  } catch (InterruptedException e) {  e.printStackTrace();  }  }  }  
}  public class ThreadDemo01 {  public static void main(String[] args) throws InterruptedException {  Thread t = new MyThread();  t.start();  while (true) {  System.out.println("hello main");  Thread.sleep(1000);  }  }  
}

结果如图所示:
在这里插入图片描述

🍓第二种:实现Runnable接口

class MyRunnable implements Runnable{  @Override  public void run(){  System.out.println("hello Runnable");  try {  Thread.sleep(2000);  } catch (InterruptedException e) {  e.printStackTrace();  }  }  
}  public class Text2 {  public static void main(String[] args) throws InterruptedException {  Thread t2 = new Thread(new MyRunnable());  t2.start();  while(true){  System.out.println("hello main");  Thread.sleep(2000);  }  }  
}

结果如图所示:
在这里插入图片描述

🍓注意事项:

我们使用Runnable接口的方式和直接继承Thread类的方式有什么区别吗?
我们使用Runnable接口的方式有利于我们进行解耦合

🍓解耦合:

那什么是解耦合?
我们在创建一个线程,是需要两个关键操作的:

  1. 明确线程要执行的任务
  2. 调用系统的API创建出线程

那么此时若我们使用的是Runable接口
那么我们就可以把任务单独提取出来,提取出来之后

就可以随时把代码改成使用其他方式来执行这个任务

举个例子:
现在有一个一家三口,父亲,母亲,和儿子,现在家里面没有酱油了,此时就需要去执行“买酱油”这个任务

那么我们就可以把“买酱油”这个任务单独提取出来,之后这个任务是交给父亲执行,还是母亲执行,还是儿子执行都是没有本质区别的

而在代码中就是把这个任务单独提取成Runnable, 后续是谁来执行都可以进行轻松的调整
这个就是解耦合

🍓Runnable

Runnable可以理解为可执行的
作用:

通过这个接口就可以抽象表示出一段可以被其他实体来执行的代码~~

在代码中的run方法就是这个Runnable要表示的一段代码

class MyRunnable implements Runnable{  @Override  public void run(){  System.out.println("hello Runnable");  try {  Thread.sleep(2000);  } catch (InterruptedException e) {  e.printStackTrace();  }  }  
}

但是这个Runnable只是一段可以执行的代码,还是要搭配Thread类,才能够真正地在系统中创建出线程
就是把线程和要执行的任务进行了解耦合:
如下所示:

Thread t = new Thread(new MyRunnable());

🍓第三种 : 继承Thread类, 重写run方法,使用匿名内部类:

匿名内部类 :

在一个类中定义的类,没有名字, 也就不能够重复使用,用一次就扔了

这个匿名内部类是Thread的子类, 同时又把这个匿名内部类的实例给创建出来

而且这个匿名内部类是可以重写run方法的

public class Demo3 {  public static void main(String[] args) throws InterruptedException {  Thread t = new Thread(){  @Override  public void run(){  while(true){  System.out.println("hello thread");  try {  Thread.sleep(1000);  } catch (InterruptedException e) {  e.printStackTrace();  }  }  }  };  t.start();  while(true){  System.out.println("hello main");  Thread.sleep(1000);  }  }  
}

🍓第四种: 实现Runnable接口, 重写run, 使用匿名内部类

public class Demo4 {  public static void main(String[] args) throws InterruptedException {  Runnable runnable = new Runnable() {  @Override  public void run() {  while (true) {  System.out.println("hello thread");  try {  Thread.sleep(1000);  } catch (InterruptedException e) {  e.printStackTrace();  }  }  }  };  Thread t = new Thread(runnable);  t.start();  while(true){  System.out.println("hello main");  Thread.sleep(1000);  }  }  
}

🍓第五种: 基于Lambda表达式(最推荐的方式)

Lambda表达式是更简洁的语法表示方式: (语法糖)

以下就是一个for循环的语法糖

for(int x : arr)

Lambda表达式如下:

Thread t= new Thread(() -> {
});

Lambda表达式:

Lambda表达式是一个匿名函数, (无名函数; 一次性的)
主要是用来实现回调函数的效果的

回调函数:

回调函数 :

不是程序员主动调用, 也不是现在立即调用
而是把调用的机会交给别人(操作系统, 库, 框架, 别人写的代码)
交给别人之后在合适的时机来进行调用

Lambda表达式的本质

Lambda表达式本质上就是一个函数式接口,通过函数式接口来描述一个方法,本质上还是没有脱离类

public class Demo5 {  public static void main(String[] args) throws InterruptedException {  Thread t = new Thread(() -> {  while(true){  System.out.println("hello thread");  try {  Thread.sleep(1000);  } catch (InterruptedException e) {  e.printStackTrace();  }  }  });  t.start();  while(true){  System.out.println("hello main");  Thread.sleep(1000);  }  }  
}

在这里插入图片描述

🍓线程的其他属性:

构造方法:

Thread(String name) :

name不会影响到线程的执行, 就只是给线程取不同的名字;为了方便调用和调试
而且线程间的名字是可以重复的,但是要起一个有意义的名字

例子:
创建一个线程, 命名为 " 这个是一个新线程"

public class Demo5 {  public static void main(String[] args) throws InterruptedException {  Thread t = new Thread(() -> {  while(true){  System.out.println("hello thread");  try {  Thread.sleep(1000);  } catch (InterruptedException e) {  e.printStackTrace();  }  }  },"这个是新线程");  t.start();  while(true){  System.out.println("hello main");  Thread.sleep(1000);  }  }  
}

在这里插入图片描述

getId() :

JVM自动分配的身份标识,会保证唯一性, 标识一个进程中唯一的一个线程
这个ID是java给你这个线程分配的ID, 不是系统API给你分配的, 也不是PCB中的ID

getState() :

线程的状态,显示线程是就绪状态还是阻塞状态)

getPriority() :

线程的优先级: 由于系统是随机调度的方式
在java中设置优先级效果不明显;只是对内核调度器的调度过程产生了一些影响:

isDaemon():

描述当前线程是否是守护线程:(后台线程)

t.setDaemon(true); //设置为后台线程

不写这个代码就默认是前台线程

🍓前台线程与后台线程的区别:

后台线程: 后台线程运行不会阻止进程结束

前台线程: 前台线程运行会阻止进程结束

我们创建的代码默认是前台线程,在运行过程中会阻止进程结束
只要前台进程没有执行完毕,那么进程就不会结束
即使main方法已经执行完毕了,进程也不会结束

如下图所示:

在这里插入图片描述

在这里插入图片描述

isAlive() :

表示内核中的线程(PCB)是否存在

java代码中定义的线程对象(Thread) 实例, 虽然表示一个线程
但是这个对象本身的生命周期和内核中PCB的生命周期不完全一样

Thread t = new Thread()

此时t对象有了,但是内核PCB还没有,isAlive 就是false

当执行了下面的代码之后,才创建了PCB:

t.start();

此时才有了内核PCB, 才真正的在内核中创建了这个PCB, 此时isAlive() 就是true

在这里插入图片描述

start()

Thread类使用start方法来启动一个线程

对于同一个Thread类来说, start()方法只能够调用一次

在这里插入图片描述

🍓start() 和 run() 方法的区别

有一个经典的面试题:
start() 和 run() 方法的区别是什么?

如果是调用run()方法来执行, 那么就没有创建新的线程
在代码中只有main这个主线程, 在代码中只有一个线程,
此时这个主线程就只能够停留在run方法中的循环里面, 一直打印hello thread

不会去执行mian方法中下方的while循环的代码,也就不会打印hellow main

如图:
在这里插入图片描述

那如果是调用的start() 方法来执行代码,则会创建一个新的线程

而这个新的线程就会去执行run()方法中的循环, 来打印hellow thread
而main方法中的主线程就会去继续向下执行下方的while循环中的代码了

也就会在循环中不断地打印hellow main , 此时是有两个线程同时执行的,

第一个是通过start()创建出来的新线程 在打印run()方法中代码
第二个是main主线程在执行while循环中的代码

如图:
在这里插入图片描述

🍓总结:

两者的区别如下:

start方法的内部是调用系统的API
在系统内核中创建出线程,然后再由这个线程去执行run方法

run方法是单纯描述了这个线程的要执行什么内容
这个run方法会在start方法创建好线程之后自动被调用
没有创建出新的线程, 只是一个run方法

在这里插入图片描述

http://www.lryc.cn/news/478878.html

相关文章:

  • 三十四、Python基础语法(文件操作-上)
  • 【大咖云集,院士出席 | ACM独立出版】第四届大数据、人工智能与风险管理国际学术会议 (ICBAR 2024,11月15-17日)--冬季主会场
  • 03 Oracle进程秘籍:深度解析Oracle后台进程体系
  • AndroidStudio通过Bundle进行数据传递
  • Linux篇(文件管理命令)
  • 大数据新视界 -- 大数据大厂之 Impala 性能优化:数据存储分区的艺术与实践(下)(2/30)
  • 【数据结构】B树
  • Docker 容器网络模式详解
  • 吴恩达深度学习笔记:卷积神经网络(Foundations of Convolutional Neural Networks)4.11
  • 小游戏开发,出现了降本增效的技术?
  • (4)Java 编程基础概览:Java中的输入输出操作与代码注释详解
  • Git使用指南
  • 【linux】再谈网络基础(一)
  • Unknown at rule @tailwindscss(unknownAtRules)
  • IDEA - 快速去除 mapper.xml 黄色警告线和背景色----简化版
  • 高级 SQL 技巧详解
  • 移除元素(java)
  • 【Linux】shell脚本:检测文件是否存在,如存在则删除
  • Git代码托管(三)可视化工具操作(1)
  • How to use ffmpeg to convert video format from .webm to .mp4
  • Halcon 从XML中读取配置参数
  • hive表内外表之间切换
  • 电子邮件营销软件哪个好?
  • OpenAI大事记;GPT到ChatGPT参数量进化
  • springboot020基于Java的免税商品优选购物商城设计与实现
  • 代码随想录之字符串刷题总结
  • PS-基础学习(常用快捷键1.2-1.3)
  • qt QListView详解
  • 287. 寻找重复数
  • 2024年最受欢迎的编程语言