文档章节

线程最最基础的知识

LieBrother
 LieBrother
发布于 09/19 08:28
字数 1785
阅读 1201
收藏 33

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() 方法。

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

推荐阅读

老板叫你别阻塞了

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

泡一杯茶,学一学同异步

进程知多少?

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

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

觉得文章有用帮忙转发&点赞,多谢朋友们!

LieBrother

© 著作权归作者所有

LieBrother
粉丝 125
博文 51
码字总数 78210
作品 0
深圳
程序员
私信 提问
加载中

评论(2)

升级中
升级中
简单明了,谢谢分享
LieBrother
LieBrother 博主
多谢支持哈
学爬虫,需要掌握哪些Python基础?

入手爬虫确实不要求你精通Python编程,但基础知识还是不能忽视的,那么我们需要哪些Python基础呢? 首先我们先来看看一个最简单的爬虫流程: 第一步要确定爬取页面的链接,由于我们通常爬取的...

糖宝lsh
2018/11/22
23
0
android基础知识02——线程安全4:HandlerThread

android的UI操作不是线程安全的,同时也只有主线程才能够操作UI,同时主线程对于UI操作有一定的时间限制(最长5秒)。为了能够做一些比较耗时的操作(比如下载、打开大文件等),android提供...

迷途d书童
2012/03/23
425
0
IOS学习笔记——Objective-c基础(一)

最近自学ios,ios的支持语言是Objective-c,所以要学习ios就需要先学习Objective-c语言。当掌握了Objective-c的基础知识之后,我们就可以入手学习iOS开发做出一些自己的应用。 我现在还是...

丛林迷雾
2012/12/30
1K
1
依赖是种病,得治

我一直在犯的错误就是太依赖于框架,各种UI框架、小插件,但凡有现成的,就不会动手去写。 一方面是自认为写不出更优秀的效果(事实也是如此),另一个方面就是自己懒,觉得看看API,5分钟就能...

花花阿姨
2017/06/02
1K
15
Java 并发工具包 | J.U.C

Java 并发工具包 | J.U.C 不知道大家还有没有印象,上次我们已经说过了,我们为了实现集合相关类的线程安全,JDK 提供了一套同步容器,也就是 Vector,Hashtable,还有一个 Collections 工具...

优惠码领取
03/14
0
0

没有更多内容

加载失败,请刷新页面

加载更多

插入排序算法

《算法总纲目录》 1、定义     元素被分为有序区和无序区两部分。最初有序区只有一个元素。每次从无序区中选择一个元素,插入到有序区的位置,直到无序区变空。 2、代码 public class...

木九天
17分钟前
2
0
ApacheCN 翻译/校对/笔记整理活动进度公告 2019.10.18

注意 请贡献者查看参与方式,然后直接在 ISSUE 中认领。 翻译/校对三个文档就可以申请当负责人,我们会把你拉进合伙人群。翻译/校对五个文档的贡献者,可以申请实习证明。 请私聊片刻(52981...

ApacheCN_飞龙
20分钟前
2
0
Hands-on! 如何给 TiDB 添加新系统表

作者:黄东旭 “TiDB,你已经是一个成熟的数据库了,该学会用自己的 SQL 查自己的状态了。” 对于一个成熟的数据库来说,通过 SQL 来查询系统本身的状态再正常不过,对于 MySQL 来说 INFOMA...

TiDB
25分钟前
2
0
SpringBoot admin+Eureka+钉钉通知

SpringBoot admin+Eureka+钉钉通知 一、效果 登录账号+密码 监控服务 查看实时日志 钉钉通知 二、什么是Spring Boot Admin ? Spring Boot Admin是一个开源社区项目,用于管理和监控SpringB...

小白的成长
28分钟前
9
0
docker-rabbitmq

docker pull rabbitmqmkdir -p /rabbitmqdocker run -d \--name rabbitmq \--hostname rabbitmq \-v /rabbitmq:/var/lib/rabbitmq \-e RABBITMQ_DEFAULT_USER=root \-e RA......

李琼涛
30分钟前
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部