程序员最近都爱上了这个网站  程序员们快来瞅瞅吧!  it98k网:it98k.com

本站消息

站长简介/公众号

  出租广告位,需要合作请联系站长


+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

2023-06(3)

[多线程] -线程的基础概念入门

发布于2020-11-19 20:49     阅读(848)     评论(0)     点赞(2)     收藏(2)


一、线程的基本概念

1. 什么是线程?什么是进程?

解释线程和进程比较枯燥的概念就是:进程是资源分配的基本单位,线程是CPU的调度的最小单位。这样的答案都太字面,抽象不太容易让人理解,所以我还是举一个曾经看到过的例子:

如果说进程是一列拉载着数据信息的火车,那么线程就是一节一节的车厢。
就像火车可以拥有多节车厢一样,一个进程可以拥有多个线程
如果一列火车发生了故障,很多情况下是不会影响到其他的火车的运行,或者说影响的范围有限。而当一节车厢发生故障的时候,这列火车就很难再继续运行。这样就很好理解一个进程奔溃后,在保护模式下对其他进程的影响是有限的,但如果是一个线程挂掉了,进程很大概率下都会崩溃。所以多进程要比多线程更加的健壮

如果理解了什么是线程,什么是进程,那么两者之间都有哪些区别呢?

2. 线程和进程的详述(劝退警告)

首先总结性的比较一下线程和进程:进程和线程都是CPU工作时间段的描述,是运行中的程序指令的一种描述,但是各自的粒度不同。
注意:以下大篇概念出没,如有困倦请跳过。

首先我们先看一个程序运行的背景
我们电脑其实就是通过CPU+RAM(内存)+外设(如显卡,光驱等)组成,一个程序的实际运行实际上就是通过CPU+RAM的组合处理的。
这里面有一个不能够被忽略的事实就是CPU中的运算单元太快了,在[计算机原理]–CPU的基础入门中我提到过ALU(运算单元)的运算能力是远大于寄存器的。更何谈缓存区和RAM。那么当多个任务需要执行怎么办?难道需要执行完第一个任务然后再去执行第二个任务?如果这样的话,CPU实际运行的时间其实和等待的时间是有着数量级的差异。如果还是不懂得话,可以理解为我们上学时期在写作业的时候,由于往作业本上写字的速度是跟不上大脑计算的速度的,当我们大脑已经计算出1+1=2的时候,可能手刚刚写完加号,而此刻大脑中负责运算的部位已经开始休息等待下一次的运算。运算可能需要1ms,而写字需要整整1s。
还有一个需要知道的事实:执行一段程序代码,实现一个功能的过程中,当得到CPU的时候,相关的资源必须已经就位,等待电脑通过调度算法决定哪个任务需要CPU来执行,此时PC(指令计数器)指向该段任务代码,CPU获取指令开始执行。
那么串联起来其实就是:CPU调取进程A,获取其中的指令,开始执行A,保存A的结果(上下文,这里不做赘述)。然后调入下一个等待执行的进程B,开始执行B。 当然如果CPU分配给程序B的时间片用完了,那么他就会暂时的将运行结果保存在上下文中,备切换出去,等待CPU的再次临幸。
背景叙述完了,那么什么是进程和线程呢?
进程就是CPU上下文切换之间的程序执行的部分,是此刻在CPU中运行程序的描述。也可以说是CPU执行时间的描述。
那么线程呢?
进程的颗粒度太大了,每次执行都要进行上下文切换。如果我们把一个软件当做是一个应用程序,那么一个软件的执行不可能只有一条逻辑组成的,必须有多个程序段的分支来实现。那么当我们运行程序时就变成了程序A的a小段代码快获得了CPU的使用权,此时CPU加载a小段的指令,开始执行a小段。当a小段执行完毕后再切换到b小段的指令执行。最后当所有代码段执行完毕后,保存程序A的运算结果,退出程序A,开始执行程序B。在这里,程序A与程序B之间的切换时间远大于代码段a与代码段b之间的切换。而代码单a和代码段b的时间总和就是程序A的运行时间。这里的代码段a,b其实就是线程,他共享了进程A的上下文环境,描述了CPU在更加细小的时间段内完成的任务。
到此不难理解,进程就是对CPU处理在一个时间段内工作状态的描述,而线程则是这段时间状态内每一条详细工作内容的描述,本质上进程和线程的区别就是对于CPU工作时间段描述的颗粒度不同。

3. Daemon Thread (守护线程)

区别于用户线程,守护线程指的是程序运行的时候在后台提供辅助服务的一些线程。我们常说的GC线程(垃圾回收线程)就是典型的守护线程。守护线程的特点就是当JVM发现没有任何业务线程的时候就会离开,守护线程也随之终止。在Java中,守护线程的实现也很简单,直接调用Thread对象的setDaemon(true)就可以。在使用守护线程的时候要注意一下三点:

  1. thread.setDaemon()要在thread.start()之前调用,否则会抛出IllegalThreadStateException正常的线程不允许被设置为守护线程。
  2. 守护线程中衍生的线程也是守护线程
  3. 守护线程不要去尝试访问固有资源,如文件,数据库。因为他可能在任何一个操作的中间就发生终止。

二、 Java线程的生命周期

在java中,我们常说的生命周期可以大概分为:

  1. 线程新建 new
  2. 线程就绪 runnable
  3. 线程运行 running
  4. 线程阻塞 blocked (线程等待 wait 也可以算在这里,实际有差异)
  5. 线程死亡 dead

具体的线程生命周期的切换可以参考下图
线程生命周期

三、在Java中如何创建线程

在jdk官方文档中,提到了线程的创建方式有两种:

  1. 继承Thread类
public class TheadDemo extends Thread {
	
	@Override
	public void run() {
		System.out.println("集成Thread类");
	}

}

  1. 实现runnable接口
public class ThreadDemo02 implements Runnable {

	@Override
	public void run() {
		System.out.println("实现runnable接口");

	}

}

4、 面试题

1) 为什么一个线程不能够两次调用start()?

Java的线程是不允许启动两次的,第二次调用必然会抛出IllegalThreadStateException,这是一种运行时异常,多次调用start被认为是编程错误。
2) start()和run()这两个方法有什么区别?

  1. 调用start()方法后,jvm会去创建一个新的子线程,然后调用run()方法启动子线程。(start方法真正的实现了多线程运行,无需等待run方法体执行完毕就可以执行下面的代码);
  2. 而如果直接调用run()方法,程序还是会使用主线程执行。由于程序只有一条线程,其执行路径只有一条,还是要顺序执行,需要等待。
    附:简单的测试代码
// 新建线程
public class TheadDemo extends Thread {
	
	@Override
	public void run() {
		println("开始执行run方法体,此时时间为:"+new Date());
		try {
			Thread.currentThread().sleep(1000l*10);
			String name = Thread.currentThread().getName();
			System.err.println("当前线程:"+name+"执行结束");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	private static void println(String message) {
		System.out.println(message);
	}
}

	public static void main(String[] args) throws InterruptedException {

		TheadDemo theadDemo = new TheadDemo();
		theadDemo.start();
		System.err.println("主流程继续运行,此时时间为:"+new Date());
		TheadDemo theadDemo2 = new TheadDemo();
		theadDemo2.run();
		System.err.println("主流程继续运行,此时时间为:"+new Date());

	}

原文链接:https://blog.csdn.net/xiaoai1994/article/details/109736302



所属网站分类: 技术文章 > 博客

作者:java之恋

链接:http://www.javaheidong.com/blog/article/1009/79780a16eeef6700e795/

来源:java黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

2 0
收藏该文
已收藏

评论内容:(最多支持255个字符)