线程最最基础的知识_线程基本知识-程序员宅基地

技术标签: java  线程  Java 多线程  

在这里插入图片描述

Java 多线程系列文章第 5 篇。

什么是线程

试想一下没有线程的程序是怎么样的?百度网盘在上传文件时就无法下载文件了,得等文件上传完成后才能下载文件。这个我们现在看起来很反人性,因为我们习惯了一个程序同时可以进行运行多个功能,而这些都是线程的功劳。

之前的文章 进程知多少 中讲到,为了实现多个程序并行执行,引入了进程概念。现在引入线程是为了让一个程序能够并发执行。

线程的组成

线程ID:线程标识符。

当前指令指针(PC):指向要执行的指令。

寄存器集合:存储单元寄存器的集合。

堆栈:暂时存放数据和地址,一般用来保护断点和现场。

线程与进程区别

线程和进程之间的区别,我觉得可以用这个例子来看出两者的不同,进程就是一栋房子,房子住着 3 个人,线程就是住在房子里的人。进程是一个独立的个体,有自己的资源,线程是在进程里的,多个线程共享着进程的资源。

线程状态

我们看到 Java 源代码里面,线程状态的枚举有如下 6 个。

public enum State {

 //新建状态
 NEW,

 //运行状态
 RUNNABLE,

 //阻塞状态
 BLOCKED,

 //等待状态
 WAITING,

 //等待状态(区别在于这个有等待的时间)
 TIMED_WAITING,

 //终止状态
 TERMINATED;
}

下面给这 6 个状态一一做下解释。

NEW:新建状态。在创建完 Thread ,还没执行 start() 之前,线程的状态一直是 NEW。可以说这个时候还没有真正的一个线程映射着,只是一个对象。

RUNNABLE:运行状态。线程对象调用 start() 之后,就进入 RUNNABLE 状态,该状态说明在 JVM 中有一个真实的线程存在。

BLOCKED:阻塞状态。线程在等待锁的释放,也就是等待获取 monitor 锁。

WAITING:等待状态。线程在这个状态的时候,不会被分配 CPU,而且需要被显示地唤醒,否则会一直等待下去。

TIMED_WAITING:超时等待状态。这个状态的线程也一样不会被分配 CPU,但是它不会无限等待下去,有时间限制,时间一到就停止等待。

TERMINATED:终止状态。线程执行完成结束,但不代表这个对象已经没有了,对象可能还是存在的,只是线程不存在了。

线程既然有这么多个状态,那肯定就有状态机,也就是在什么情况下 A 状态会变成 B 状态。下面就来简单描述一下。

结合下图,我们 new 出线程类的时候,就是 NEW 状态,调用 start() 方法,就进入了 RUNNABLE 状态,这时如果触发等待,则进入了 WAITING 状态,如果触发超时等待,则进入 TIMED_WAITING 状态,当访问需要同步的资源时,则只有一个线程能访问,其他线程就进入 BLOCKED 状态,当线程执行完后,进入 TERMINATED 状态。
在这里插入图片描述
其实在 JVM 中,线程是有 9 个状态,如下所示,有兴趣的同学可以深入了解一下。

javaClasses.hpp
enum ThreadStatus {
    NEW = 0,
    RUNNABLE = JVMTI_THREAD_STATE_ALIVE + // runnable / running
                               JVMTI_THREAD_STATE_RUNNABLE,
    SLEEPING = JVMTI_THREAD_STATE_ALIVE + // Thread.sleep()
                               JVMTI_THREAD_STATE_WAITING +
                               JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT +
                               JVMTI_THREAD_STATE_SLEEPING,
    IN_OBJECT_WAIT = JVMTI_THREAD_STATE_ALIVE + // Object.wait()
                               JVMTI_THREAD_STATE_WAITING +
                               JVMTI_THREAD_STATE_WAITING_INDEFINITELY +
                               JVMTI_THREAD_STATE_IN_OBJECT_WAIT,
    IN_OBJECT_WAIT_TIMED = JVMTI_THREAD_STATE_ALIVE + // Object.wait(long)
                               JVMTI_THREAD_STATE_WAITING +
                               JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT +
                               JVMTI_THREAD_STATE_IN_OBJECT_WAIT,
    PARKED = JVMTI_THREAD_STATE_ALIVE + // LockSupport.park()
                               JVMTI_THREAD_STATE_WAITING +
                               JVMTI_THREAD_STATE_WAITING_INDEFINITELY +
                               JVMTI_THREAD_STATE_PARKED,
    PARKED_TIMED = JVMTI_THREAD_STATE_ALIVE + // LockSupport.park(long)
                               JVMTI_THREAD_STATE_WAITING +
                               JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT +
                               JVMTI_THREAD_STATE_PARKED,
    BLOCKED_ON_MONITOR_ENTER = JVMTI_THREAD_STATE_ALIVE + // (re-)entering a synchronization block
                               JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER,
    TERMINATED = JVMTI_THREAD_STATE_TERMINATED
};

Java 线程实现

下面讲一讲在 Java 中如何创建一个线程。众所周知,实现 Java 线程有 2 种方式:继承 Thread 类和实现 Runnable 接口。

继承 Thread 类

继承 Thread 类,重写 run() 方法。

class MyThread extends Thread {

    @Override
    public void run() {
        System.out.println("MyThread");
    }

}

实现 Runnable 接口

实现 Runnable 接口,实现 run() 方法。

class MyRunnable implements Runnable {

    public void run() {
        System.out.println("MyRunnable");
    }

}

这 2 种线程的启动方式也不一样。MyThread 是一个线程类,所以可以直接 new 出一个对象出来,接着调用 start() 方法来启动线程;而 MyRunnable 只是一个普通的类,需要 new 出线程基类 Thread 对象,将 MyRunnable 对象传进去。

下面是启动线程的方式。

public class ThreadImpl {

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        Thread myRunnable = new Thread(new MyRunnable());
        System.out.println("main Thread begin");
        myThread.start();
        myRunnable.start();
        System.out.println("main Thread end");
    }

}

打印结果如下:

main Thread begin
main Thread end
MyThread
MyRunnable

看这结果,不像咱们之前的串行执行依次打印,主线程不会等待子线程执行完。

敲重点:不能直接调用 run(),直接调用 run() 不会创建线程,而是主线程直接执行 run() 的内容,相当于执行普通函数。这时就是串行执行的。看下面代码。

public class ThreadImpl {

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        Thread myRunnable = new Thread(new MyRunnable());
        System.out.println("main Thread begin");
        myThread.run();
        myRunnable.run();
        System.out.println("main Thread end");
    }

}

打印结果:

main Thread begin
MyThread
MyRunnable
main Thread end

从结果看出只是串行的,但看不出没有线程,我们看下面例子来验证直接调用 run() 方法没有创建新的线程,使用 VisualVM 工具来观察线程情况。

我们对代码做一下修改,加上 Thread.sleep(1000000) 让它睡眠一段时间,这样方便用工具查看线程情况。

调用 run() 的代码:

public class ThreadImpl {

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.setName("MyThread");
        Thread myRunnable = new Thread(new MyRunnable());
        myRunnable.setName("MyRunnable");
        System.out.println("main Thread begin");
        myThread.run();
        myRunnable.run();
        System.out.println("main Thread end");
        try {
            Thread.sleep(1000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

class MyThread extends Thread {

    @Override
    public void run() {
        System.out.println("MyThread");
        try {
            Thread.sleep(1000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

class MyRunnable implements Runnable {

    public void run() {
        System.out.println("MyRunnable");
        try {
            Thread.sleep(1000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

运行结果:

main Thread begin
MyThread

在这里插入图片描述
只打印出 2 句日志,观察线程时也只看到 main 线程,没有看到 MyThreadMyRunnable 线程,印证了上面咱们说的:直接调用 run() 方法,没有创建线程

下面我们来看看有
调用 start() 的代码:

public class ThreadImpl {

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.setName("MyThread");
        Thread myRunnable = new Thread(new MyRunnable());
        myRunnable.setName("MyRunnable");
        System.out.println("main Thread begin");
        myThread.start();
        myRunnable.start();
        System.out.println("main Thread end");
        try {
            Thread.sleep(1000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
	
}

运行结果:

main Thread begin
main Thread end
MyThread
MyRunnable

在这里插入图片描述
所有日志都打印出来了,并且通过 VisualVM 工具可以看到 MyThreadMyRunnable 线程。看到了这个结果,切记创建线程要调用 start() 方法。

今天就先讲到这,继续关注后面的内容。

推荐阅读

线程最最基础的知识

老板叫你别阻塞了

吃个快餐都能学到串行、并行、并发

泡一杯茶,学一学同异步

进程知多少?

设计模式看了又忘,忘了又看?

后台回复『设计模式』可以获取《一故事一设计模式》电子书

觉得文章有用帮忙转发&点赞,多谢朋友们!
在这里插入图片描述

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u011642663/article/details/101010668

智能推荐

MSP432P401R 速成(电赛必备)-程序员宅基地

文章浏览阅读3.7k次,点赞16次,收藏195次。【代码】MSP432P401R 速成(电赛必备)_msp432p401r

WebView 加载页面空白及加载不全问题_webview2 网页元素加载不全 c#-程序员宅基地

文章浏览阅读5.2k次。webview加载页面不全或webview加载空白_webview2 网页元素加载不全 c#

SkyWalking8.7源码解析(五):链路基本知识、链路ID生成、TraceSegment、Span基本概念、Span完整模型、StackBasedTracingSpan_skywalking spanid-程序员宅基地

文章浏览阅读4.1k次,点赞11次,收藏19次。21、链路基本知识 上图是一个下单接口的链路,在链路中首先要理解的概念是Segment,Segment表示一个JVM进程内的所有操作,上图中有6个Segment。Gateway Segment是Mall Segment的parent,通过parent关系就可以把多个Segment按顺序拼起来组装成一个链路 一个Segment里可能发生多个操作,如上图Segment中操作1是查Redis,操作2是查MySQL,这就是两个Span,Span表示一个具体的操作。Span之间也是基于parent的关系构建起_skywalking spanid

Flask Python:如何获取不同请求方式的参数_python flask 获取客户端请求的参数-程序员宅基地

文章浏览阅读1.1k次,点赞13次,收藏18次。flask,python如何获取不同请求方式的参数,如何获取post请求的参数,get_data(),get_json()的使用_python flask 获取客户端请求的参数

苦难是人生最大的财富_苦难是我最大的财富-程序员宅基地

文章浏览阅读1.9k次。苦难是人生最大的财富  ——少 年 求 伯 君 求伯君,这个响亮的名字,现在早已经被全国少年电脑迷所熟知,一大批有志于IT业发展的莘莘学子,暗中以他的成功经历为楷模,渴望像他那样创业,用少年英雄的大手笔,为中国电脑软件业增添一道亮丽的风景线。称求伯君为“少年英雄”毫不为过:——1986年,他完成自己的处女作“西山打印系统”,毅然辞职“下海”时,年仅22岁。——1989年,WPS横空出世,继而风靡全国,迅_苦难是我最大的财富

华为mate10科学计算机,手机替代电脑为时过早?看华为Mate 10教你轻办公-程序员宅基地

文章浏览阅读178次。PC行业无疑已经遇到了瓶颈,从2011年至今,它已经经历了超过6年的市场寒冬,连续12个季度的下滑让整个行业销量萎缩了30%(IDC数据)。短时间的波动可以解释为市场的原因,但是横跨数年的持续下滑只能向PC自身深究,那就是曾经代表先进生产力的传统PC已经无法跟上时代,更无法满足消费者的需求。面对智能手机与平板电脑的激烈竞争,这几年的PC又一次在生动地实践着“落后就要挨打”这份真理,昔日的王者将亡未..._哪款手机替代电脑办公好用

随便推点

一文盘点深度学习13个常见问题(附详细解答&学习资源)-程序员宅基地

文章浏览阅读2k次,点赞2次,收藏6次。作者:VIDHYA小组翻译:陈之炎校对:顾佳妮本文共4700字,建议阅读10+分钟。本文为你解答关于入门深度学习的问题,并列出了大量的资源让你起步学习。概述从Facebo..._深度学习初学者常有的疑问

【数字IC/FPGA】书籍推荐(1)----《轻松成为设计高手--Verilog HDL实用精解》_轻松成为设计高手verilog-程序员宅基地

文章浏览阅读3.3w次,点赞32次,收藏35次。【数字IC/FPGA】书籍推荐(1)----《轻松成为设计高手--Verilog HDL实用精解》_轻松成为设计高手verilog

LK流程-----基于MTK平台_lk mtk i2c 6762-程序员宅基地

文章浏览阅读3.8k次,点赞5次,收藏27次。一、LK简介Lk的主要功能: 1、初始化硬件模块,比如时钟,中断,UART,USB,LCD,PMIC,eMMC等。打开MMU,使能I/D-cache,加速lk执行,显示logo、充电相关。 2、从emmc的boot分区取出boot.img解压,将根文件系统(ramdisk)、zImage加载到DRAM; 3、解析dtb,写入到DRAM指定区域; 4、关闭MMU、irq_lk mtk i2c 6762

frps 多个_使用frp工具实现内网的穿透以及配置多个ssh和web服务-程序员宅基地

文章浏览阅读1.3k次。frp简介frp 是一个可用于内网穿透的高性能的反向代理应用,支持 tcp, udp 协议,为 http 和 https 应用协议提供了额外的能力,且尝试性支持了点对点穿透。环境准备ssh连接1. 需要一台可以直接访问外网的服务器,比如阿里云服务器(服务端)2. 需要做内网穿透的服务器,比如公司内部的局域网测试服务器(客户端)web访问3. 需要额外的已经备案的域名下载地址安装步骤客户端跟服务端都..._frps多对一

哈夫曼字符串编码c语言实现,哈夫曼编码-C语言实现-程序员宅基地

文章浏览阅读2.2k次,点赞2次,收藏19次。实验目的:(1) 掌握二叉树的定义;(2) 掌握哈夫曼树和哈夫曼编码算法的实现。???实验内容:实现一个哈夫曼编码系统,系统包括以下功能:(1) 字符信息统计:读取待编码的源文件SourceFile.txt,统计出现的字符及其频率。附:SourceFile.txt文件内容为U ARE THE BEST IN MY HEART(2) 建立哈夫曼树:根据统计结果建立哈夫曼树。(3) 建立哈夫曼码表:利..._给出字符串中每个字符的哈夫曼编码的码字c语言

java输出流文件_Java基础之文件的输入输出流操作-程序员宅基地

文章浏览阅读1.2k次。在介绍输入输出流之前,首先需要了解如何创建文件,创建文件夹以及遍历文件夹等各种操作,这里面不在一一介绍,主要介绍的是文件的输入输出流操作。在起初学习文件操作之前,总是喜欢将输入输出弄混淆,后来通过看一些书以及一些视频,有了一些自己的心得体会,随后介绍一下,以便在我以后忘记的时候可以温习一下。1.流的概念及分类Java将所有传统的流模型(类或抽象类),都放在了Java.io包中,用来实现输入输出的功..._java输出流输出文件

推荐文章

热门文章

相关标签