当前位置:网站首页>Thread线程类基本使用(上)
Thread线程类基本使用(上)
2022-08-02 19:41:00 【小李爱吃川菜】
前言:
知道了什么是进程与线程以及进程与线程的区别后,我们就来学习一下我们Java中是如何创建线程并使用线程的,什么你还不清楚什么进程与线程的概念区别,来请看这里进程与线程详解
目录
1.创建线程(5种方式)
其实Java代码中我们平常好像并没有使用到线程,其实不然,即使我们只打印一个helloword我们也是要使用到线程的,在执行一个Java文件时,会自动创建若干线程例如每个Java文件执行时都会有一个主线程去调用main方法,但如果我们要自己创建线程那就要用到Java提供的类,在Java中要想创建新线程,Java官方为我们提供了Thread线程类,通过多种方法例如自己编写一个类继承Thread类重写其中的Run()方法,实现我们自己的新线程类,然后再通过实例化我们自己的新线程类调用start()方法,此时我们就真正的在操作系统内核中创建好了一个新线程。
注意
当我们使用自己重写Thread类中Run方法实现的线程类创建好的线程对象时,此时并没有真正的在操作系统内核中创建一个新线程,只能说此时我们只是明确了这个新线程的执行任务是什么,怎么理解呢?也就是说我们重写Run方法的目的是为了描述我们这个新线程是干嘛的,这就好比一份"岗位招聘要求",如果要在操作系统内核中真正的创建一个新线程,那就要用我们实例化好的对象调用start方法,此时操作系统内核才会创建线程PCB并将其添加到系统就绪链表中,随时参与系统调度,此时呢才能叫作创建好了一个新线程。
方式1:通过继承Thread重写Run方法创建线程
class Mythread1 extends Thread{
@Override
public void run() {
System.out.println("创建线程的方式1");
while(true);
}
}
public class CreateThread{
public static void main(String[]args){
//实例化线程对象
Mythread1 t=new Mythread1();
}
}
方式2:通过实现接口Runable,重写Run方法创建线程
class Mythread2 implements Runnable{
@Override
public void run() {
System.out.println("创建线程的方式2");
while(true);
}
}
public class CreateThread{
public static void main(String[]args){
//传入已实现Runable的对象,实例化线程对象
Thread mythread2=new Thread(new Mythread2());
}
}
这种方式的优点在于,将线程任务与线程之间分隔开,降低了代码的耦合度,而我们下代码的表标准就是低耦合,高内聚。
方式3:继承 Thread, 重写 run, 使用匿名内部类
public class CreateThread{
public static void main(String[]args){
//使用继承Thread重写Run匿名内部类的方式创建新线程
Thread Mythread3=new Thread(){
@Override
public void run() {
System.out.println("这是创建新线程的方式3");
while(true);
}
}
}
使用匿名内部类的优点在于,代码简化,且同时实现了,继承,重写,实例化对象的有点,所以这种方式还是比较推荐的。
方式4:实现 Runnable, 重写 run, 使用匿名内部类
public class CreateThread{
public static void main(String[]args){
//实现Runable接口重写Run方法匿名内部类的方式创建线程
Thread Mythread4=new Thread(new Thread(){
@Override
public void run() {
System.out.println("这是创建新线程的方式4");
while(true);
}
});
}
}
方式5:使用 lambda 表达式
public class CreateThread{
public static void main(String[]args){
//第五种。使用lambda表达式创建线程
Thread Mythread5=new Thread(()->{
System.out.println("这是创建线程的方式5");
while(true);
});
}
}
2.第一个多线程程序
在多线程程序里我们要注意几点就是,多线程程序中的每一个线程都是一个独立的"执行流",他们是"并发"执行的,在编写多线程程序时,我们一定要跳出写普通程序的"代码顺序执行的"固定思维,在多线程程序中,代码的前后顺序其实并不能完全说明是逻辑的执行顺序,正是由于多线程的"随机调度"和"抢占式"执行的特点,所以我们在写多线程程序时一定要小心。
演示程序
class Mythread extends Thread{
@Override
public void run() {
while(true){
System.out.println("新进程正在运行中");
try {
//通过调用Thread的静态成员方法,让线程进入阻塞状态
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class demo1 {
public static void main(String[] args) throws InterruptedException {
Thread mythread=new Mythread();
mythread.start();
while(true){
System.out.println("main主线程运行中");
Thread.sleep(3000);
}
}
}
运行结果
从以上多线程代码中运行结果可知,在主线程创建好了新线程之后,新线程与主线程之间是"并发执行","随机调度"的,实际上在主线程执行完 mythread.start();后,新线程先后经历了"创建","加载","执行",在这段时间内主线程是继续往下执行的。
注意
在多线程程序中,有几个非常重要的补充点,那就是线程的创建顺序并不代表实际上的线程先后执行顺序,也就是说先创建的线程并不一定比后创建好的线程先执行,这是因为操作系统内核中"调度器",的随即调度的特性所决定,况且调度器的这种"坏脾气"我们还不能拿他怎么办,所以你永远不知道哪个线程先执行哪个线程先结束,最多就控制线程之间结束的先后顺序(join)和调用Sleep方法让线程进入阻塞状态(此期间该线程不参与调度),正是因为调度器,多线程程序才会出现很多不可预料的执行过程。
3.查看当前进程(Java程序)线程
我们如果想要查看当前Java程序进程的所有线程信息时,可以借助JDK给我们提供的jconsole.exe来查看进程中的所有线程信息。
首先要找到这个可执行程序在哪里,他呢在JDK目录下的bin文件夹中例如我的是这样
第二部打开它,连接到本地正在执行的Java程序
当然有可能你发现你的Java程序正在运行,但是进程列表中找不到,那可能是你的权限不够,那你就重新使用右键用"以管理员身份"打开就行,之后我们就来到这个页面
在这里九可以查看当前进程的所有线程信息
注意
在这里可以看到的最重要的一个信息就是每个线程的执行的调用栈,当我们程序不小心死机卡死的情况下,我们就可以根据线程的调用栈初步确定导致程序出问题的原因。
4.start()与run()区别(面试重点)
关于这两个方法的基本区别我们在开头已经简单了解,由于这个在面试中是高频点,所以还是要介绍一下
1.直接调用run方法,操作系统内核中并不会创建线程PCB,而只是在原来的线程中执行了run方法的内容。
2.调用start方法,操作系统会创建线程PCB。并将其添加到就绪链表中,参与调度,当创建好了线程之后,会自动执行run方法内的内容。
5.Thread的常见属性及获取方法
1.ID 是线程的唯一标识,不同线程不会重复、
2.名称是各种调试工具用到
3.状态表示线程当前所处的一个情况,下面我们会进一步说明(详情见下方)
4.优先级高的线程理论上来说更容易被调度到
5.关于后台线程(守护线程),需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行(详情见下方)。
6.是否存活,即简单的理解,为 run 方法是否运行结束了,可以认为是处于不是 NEW 和 TERMINATED 的状态都是活着
的。7.线程的中断问题,下面我们下一篇文章说明()
6.前台线程与后台线程
我们在程序中自己创建的新线程,默认为前台线程,如果要修改为后台进程,那就要调用方法setDaemon()进行修改,一定要注意使用setDaemon()时一定要在调用start方法前使用,不然设置是无效的,且会抛出异常,那前台进程与后台线程的区别是什么呢?
一个Java进程中,一个Java进程要想结束,那就一定要保证进程中的所有前台进程都全部结束之后才能结束,而不用在意后台线程的执行情况。
public class protectThread {
public static void main(String[] args) throws InterruptedException {
System.out.println("main主线程,begin");
Thread t1=new Thread(()->{
System.out.println("前台线程,begin");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("前台线程,end");
});
t1.start();
Thread t2=new Thread(()->{
//保证后台线程的run执行时间大于前台线程t1
System.out.println("后台线程,begin");
try {
//让后台线程休眠较长时间
//保证主线程和前台线程t1都可快速执行完
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("后台线程,end");
});
//设置后台线程要在start方法前
t2.setDaemon(true);
t2.start();
System.out.println(t2.isDaemon());
System.out.println("main主线程,end");
}
}
7.线程的状态属性(重点)
在我们的Java的JVM中有比操作内核系统中更加详细的线程状态,且这部分内容也是十分重要的,那线程都有哪些状态呢?
1.NEW:安排了线程任务, 但还未开始行动,已创建Thread对象,但是还没有调用start方法,此时系统内核中还没有创建相应线程的PCB。
2.RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作,此时系统内核中已创建好了PCB,可能已经在CPU上执行,也可能在就绪链表中等待随机调度。
3.BLOCKED(阻塞状态1): 这几个都表示排队等着其他事情,等待锁而产生的阻塞(后边介绍锁的时候讲)
4.WAITING(阻塞状态2): 这几个都表示排队等着其他事情,线程中使用了方法wait导致的阻塞(后面讲)
5.TIMED_WAITING(阻塞状态3): 这几个都表示排队等着其他事情,休眠诸塞也就是调用了方法sleep。
6.TERMINATED: 工作完成了,系统中的线程PCB销毁,也就是线程的run方法已经执行完了。
线程状态之间的转换图
好哒,这些就是全部内容啦!
边栏推荐
猜你喜欢
Meta 与苹果的元宇宙碰撞
成为黑客不得不学的语言,看完觉得你们还可吗?
Parse the commonly used methods in the List interface that are overridden by subclasses
Metaverse 001 | Can't control your emotions?The Metaverse is here to help you
J9 Digital Currency Theory: Identifying Web3's New Scarcity: Open Source Developers
Brain-computer interface 003 | Musk said that he has realized a virtual self-dialogue with the cloud, and related concept shares have risen sharply
Redis 5 种数据结构及对应使用场景
AI Scientist: Automatically discover hidden state variables of physical systems
服务器Centos7 静默安装Oracle Database 12.2
如何解决图像分类中的类别不均衡问题?不妨试试分开学习表征和分类器
随机推荐
SQL 入门之第一讲——MySQL 8.0.29安装教程(windows 64位)
ALV概念讲解
Based on OpenGL glaciers and firebird (illumination calculation model, visual, particle system)
MySQL安装(详细,适合小白)
让你的应用完美适配平板
【LeetCode】1161. 最大层内元素和
es 官方诊断工具
成为黑客不得不学的语言,看完觉得你们还可吗?
You want the metagenomics - microbiome knowledge in all the (2022.8)
golang刷leetcode 动态规划(13) 最长公共子序列
溜不溜是个问题
4 kmiles join YiSheng group, with more strong ability of digital business, accelerate China's cross-border electricity full domain full growth
线性表(顺序表和链表)
基于 flex 布局实现的三栏布局
PG's SQL execution plan
leetcode刷题记录:7.整数反转,8.字符串转整数,9.回文数
Leetcode刷题——23. 合并K个升序链表
J9数字论:互联网跨链桥有什么作用呢?
即时通讯开发移动端网络短连接的优化手段
Leetcode刷题——字符串相加相关题目(415. 字符串相加、面试题 02.05. 链表求和、2. 两数相加)