`

线程:从模糊到清晰的要点理解

阅读更多
线程:从模糊到清晰的要点理解

        线程真的很难学,起码在我进行写这篇博文之前,我是这么认为的。甚至,我还对线程产生了恐惧,更别说多线程了。但是,经过我的再次学习,我对线程的认识终于算是棱角分明了。这里,我从以下几个方面来认识线程:
        一、线程是什么
        二、多线程是什么
        三、线程的优先级是什么意思
        四、线程的启动方法是start(),还是run()
        五、线程的状态
一、线程是什么
        线程是一个程序内部的顺序控制流。在这里,我是通过将线程和进程进行比较来理解线程的。
线程和进程的区别:
(1)每个进程都有独立的代码和数据空间,进程间的切换会有较大的开销。
(2)线程可以看成是轻量级的进程,同一类线程共享代码和数据空间,每个线程有独立的程序计数器(PC),线程切换的开销较下。
(3)多进程:在操作系统中能同时运行多个任务,即启动多个程序。
(4)多线程:在统一应用程序中有多个顺序流同时执行

        确切的说进程是不可以执行的,进程是一个静态的概念,但我们所说的进程的执行指的是进程中的主线程main方法的执行,进程执行时,其实是线程的执行。Windows、Linux、Unix都是多进程多线程的操作线系统,而dos是单进程的操作系统。
VM启动时,会有一个主方法(public static void main(String[] args))所定义的线程。main方法又叫主线程。但是在实际中,一个程序中往往包含不止一个线程,即多线程。

二、多线程是什么
        在这里,我有必要阐述一下多线程的概念。之前我理解的多线程是在一个时间点上,在一台计算机中有多个任务在同时在执行。当然,这样理解也没错,但是,我们得注意到这样理解的一个前提,那就是这台计算机是双核或者是多CPU。如果是只有一个CPU,那我们就要换种方法来理解多线程了。
        在同一个时间点上,一个CPU只支持执行一个线程,但我们所谓的多线程执行,是因为CPU的执行速度太快,而且,CPU是分块交替执行,即把每个线程划分成很多个块,执行完这个线程的某一块之后,又去执行下一个线程的某一块,一段时间过后,每一个线程都有被执行到,但是这个一段时间是人的眼睛无法衡量的,所以给人们一个错觉:多个线程是同时执行的。当然真正的多线程同时执行也是存在的,比如说你的电脑是两个CPU或者是多核,就可以多线程执行。
        我们来进行下面这个测试:main方法和Thread线程的run方法各执行一段代码(for循环),看输出结果如下:
/**
 * main()方法和线程start()方法测试类
 * @author CHP
 *
 */
public class ThreadTest {
	public static void main(String[] args){
		RunnerTest thr=new RunnerTest();
		new Thread(thr).start();
		for(int i=0;i<10;i++){
			System.out.println("main()--执行:"+i);
		}
	}
}

class RunnerTest implements Runnable{
	@Override
	public void run() {
		for(int i=0;i<10;i++){
			System.out.println(">>RunnerTest执行:"+i);
		}
	}
}

输出结果为:
           main()--执行:0
           main()--执行:1
           main()--执行:2
           main()--执行:3
           >>RunnerTest执行:0
           main()--执行:4
           main()--执行:5
           main()--执行:6
           main()--执行:7
           main()--执行:8
           >>RunnerTest执行:1
           main()--执行:9
           >>RunnerTest执行:2
           >>RunnerTest执行:3
           >>RunnerTest执行:4
           >>RunnerTest执行:5
           >>RunnerTest执行:6
           >>RunnerTest执行:7
           >>RunnerTest执行:8
           >>RunnerTest执行:9
从结果中可以看出main方法这个主线程和RunnerTest这个线程是交替执行的,也就是说,当RunnerTest启动(.start())后,这个测试中有两个线程在运行,CPU在两个线程之间交替执行,执行示意图如下:


说到这里,我算是明白了:线程有两个主要方法run()和start()。
三、线程的优先级是什么意思
        在上面我解释说多线程其实是CPU对线程的分块交替执行,在此,我想说明一点,在线程中存在一个线程优先级的概念。Java提供一个线程调度器来监控程序中的启动后进入就绪状态的所有线程。线程调度器按照线程的优先级决定应调度哪一个线程来执行。因此,我们可以提出这样的疑问:如果给线程设置了优先级,是否就可以改变线程的执行顺序呢?我们看下面的例子:
public class Test {
	public static void main(String[] args){
		Thread thr1=new Thread(new RunnerTest1());
		Thread thr2=new Thread(new RunnerTest2());
		thr1.start();
		thr2.start();
	}
}

class RunnerTest1 implements Runnable{
	@Override
	public void run() {
		for(int i=0;i<10;i++){
			System.out.println(">>1执行:"+i);
		}
	}
}
class RunnerTest2 implements Runnable{
	@Override
	public void run() {
		for(int i=0;i<10;i++){
			System.out.println("----------2执行:"+i);
		}
	}
}

执行结果是:
           >>1执行:0
           ----------2执行:0
           ----------2执行:1
           >>1执行:1
           >>1执行:2
           >>1执行:3
           >>1执行:4
           >>1执行:5
           >>1执行:6
           >>1执行:7
           >>1执行:8
           >>1执行:9
           ----------2执行:2
           ----------2执行:3
           ----------2执行:4
           ----------2执行:5
           ----------2执行:6
           ----------2执行:7
           ----------2执行:8
           ----------2执行:9
        经过多次测试,从输出结果我们可以会发现,线程1和线程2执行的时间间隔差不多。但是,如果提高线程1的优先级,结果就不是那么回事了,我们可以明显的看出线程1每次执行的时间长于线程2(为使结果明显,建议把循环的数值设置大一点)。也就是说线程的优先级确实可以改变线程的执行顺序。
        但是,这种改变也不是绝对的。在Java中Thread.setPriority()只是应用与局部的优先级,不是在整个可能的范围内设定优先级。比如说,在我们启动线程时,可能会有多个级别的线程在运行,但是我们都希望控制鼠标的这个线程不被其他线程抢占资源。Thread.setPriority()可能会根本不做任何事,这根你的早做系统和虚拟版本有关,具体猫腻,有待研究。因此,优先级对想成运行顺序的影响并不能靠我们主观臆测来判断。

四、线程的启动方法是start(),还是run()
        我在想,在上面的例子中,如果直接用Runnable的实现子类对象直接去调用run()方法,让线程去执行方法体中的方法,这种方法和之前用Thread线程调用start()方法来执行run()方法体中的内容有什么区别呢?
        这里我们再做一个测试:
public class ThreadTest {
	public static void main(String[] args){
		RunnerTest thr=new RunnerTest();
		thr.run();
//		new Thread(thr).start();
		for(int i=0;i<100;i++){
			System.out.println("main()--执行:"+i);
		}
	}
}

class RunnerTest implements Runnable{
	@Override
	public void run() {
		for(int i=0;i<100;i++){
			System.out.println(">>RunnerTest执行:"+i);
		}
	}
}

        还是刚才那个例子,唯一不同的地方是我们用Runnable的实现类对象调用线程的run()方法来启动线程,见下划线标注处。这样执行的结果如下:
>>RunnerTest执行:0
>>RunnerTest执行:1
>>RunnerTest执行:2
>>RunnerTest执行:3
>>RunnerTest执行:4
>>RunnerTest执行:5
>>RunnerTest执行:6
>>RunnerTest执行:7
>>RunnerTest执行:8
>>RunnerTest执行:9
main()--执行:0
main()--执行:1
main()--执行:2
main()--执行:3
main()--执行:4
main()--执行:5
main()--执行:6
main()--执行:7
main()--执行:8
main()--执行:9
        从结果中,我们可以看出,在整个程序中,CPU是把run()方法体中的代码全部执行完了之后再去执行main()方法体中的for循环,整个执行过称是一个线性的。也就是说当CPU去执行run()方法时,main方法中的其他语句是处于等待状态的,当run()方法执行结束,main方法接收到run()方法的返回值之后,main()方法继续执行其他语句,整个执行过程如下图所示:


        所以,thr.run()是方法的调用,效果相当于,start()后,再join(millis)(等待该线程终止的最长时间为millis毫秒,即相当于合并为一个线程);并不等同于start()方法。对于start()方法,解释是:Causes this thread to begin execution; the Java Virtual Machine calls the <code>run</code> method of this thread.也就是说,start()方法是通知计算机CPU现在有一个线程要启动啦,JVM准备启动一个线程,执行run()方法,而不是由main()主线程去调用run()方法。

五、线程的状态
线程分为5个状态,创建,就绪,运行,阻塞,终止



        在这里我重点想讲述一下线程的终止。终止线程最直接的方法就是控制run()方法体的执行条件,因此,我们只要给run()方法体的方法加一个执行条件,我们只要让条件不满足,就可以终止一个线程。我当时在做一个线程游戏时,由于需要,我要终止一个线程,我当时使用了多种方法来实现这个功能,现在我来具体分析下这几个线程:stop(),interrupted(),yield()
         1.interrupt():中断线程。这种方法虽然可以中断线程,但是很暴力,也就说,无论是线程处于运行状态还是处于睡眠状态,它都会强行中断。如果线程正在打开一个资源时,啪,线程被打断了,这就很不好;再比如说,你正在睡觉,突然一盆凉水泼在你身上,这很难受的,对于线程,它也受不了啊。所以,这不是最好的方法。
         2.stop():终止线程,当使用这种方法时,会显式stop(),这表示,这个方法不推荐使用,甚至是尽量别去使用。对于这个方法我的理解是,stop()的作用是一棒子打死。简直就是interrupt()方法的暴力升级版。起码,interrupt()还会将线程交给InterruptedException去处理,而stop()是不给你喘息的机会,一刀毙命。当我们的计算机卡死时,我们会打开任务管理器,直接结束某个进程,这个时候,我们就是在使用stop()。
         3.yield():暂停当前正在执行的线程。执行到某一个点的时候,yield()发挥其高风亮节的作风,把CPU让出来,让其他的线程去执行,但是,这并不是无条件让出,因此,yield还会再执行的。不算是线程终止方法。

          到目前为止,线程曾是最让我头疼的一个环节。因为我根本没办法理解线程到底是怎么达到想要的执行效果的。让我头疼的是,明明我想的是应该执行这个内容,但最后输出的结果却并不一定是我想的那样。因此,我重新学习了以上几点内容,现在终于算是明朗了。不知道你对线程的理解清楚了没,加油啊!
  • 大小: 16 KB
  • 大小: 13.7 KB
  • 大小: 14 KB
1
0
分享到:
评论

相关推荐

    方便自己使用的文档,编程过程中的总结

    特别是在拨测终端的开发过程中,从最初的协议设计到最后的功能实现,都积累了宝贵的经验。面对新技术和新挑战,不断学习和适应是提升个人能力的关键。未来还需继续深化对多线程编程、链表优化等方面的研究,以提高...

    JAVA基础面试题大全整理版本

    4. **主动沟通**:遇到模糊不清的问题时,可以向面试官询问更多细节以确保理解正确。 5. **自信表现**:即使遇到不熟悉的问题也不要紧张,诚实表达自己的观点和想法。 #### 五、结语 Java作为一门广泛应用的语言,...

    java课堂笔记_自己总结的

    解决方法是将系统的日期设置到一个较早的时间点,使试用期重新生效。 3. **数据库卸载**: 若数据库无法正常启动,应避免直接通过控制面板卸载,而是应该通过重新运行安装程序并选择卸载选项来进行。 4. **超级管理员...

    C++编程规范_101条规则、准则与最佳实践

    变量、函数和类的命名应反映其功能或用途,避免使用模糊或误导性的名字。 2. **注释与文档**:良好的注释能够帮助他人理解你的代码。每个函数、类和重要的代码块都应有简短的注释说明其目的和使用方式。同时,使用...

    c++高质量编程规范

    1. 明确性优先:代码应清晰易懂,避免过于复杂或晦涩的表达方式。 2. 可读性:代码应具有良好的可读性,便于他人阅读和理解。 3. 避免错误:通过使用异常处理、初始化、边界检查等方式减少潜在错误。 4. 代码复用:...

    Intel汇编语言程序设计 第四版

    这本书由东北大学软件学院编写,虽然描述中提到内容可能有些模糊,但通过超星阅读器等工具仍能清晰地获取其知识要点。 Intel汇编语言是针对Intel架构处理器的一种低级编程语言,它直接对应于机器指令,对于理解...

    Cheat Engine 7.4

    通过快速扫描、模糊扫描和连续扫描等不同方法,可以定位到想要修改的数据地址。 2. 数据类型支持:Cheat Engine支持多种数据类型,包括整型、浮点型、双精度浮点型、字符串等,这使得用户能更灵活地处理各种游戏...

    MemRepair.rar

    通过理解其工作原理,我们可以学习到如何在C++环境下实现跨进程内存操作,这对于游戏开发、逆向工程和调试等领域都有深远的影响。同时,这也提醒我们,虽然内存修改器能带来便利,但滥用可能导致不公平的游戏体验,...

    精典源码delphi源码下载 vod点歌系统.zip

    - **歌曲检索**:通过数据库查询,实现快速查找和筛选歌曲,可能涉及到模糊搜索、分类搜索等功能。 - **播放控制**:包括播放、暂停、停止、快进、快退、音量调节等操作,可能需要与媒体播放器组件进行交互。 - *...

    卡拉OK点歌系统 c# 源码

    在本文中,我们将深入探讨使用C#编程语言实现的卡拉OK点歌系统的源码,解析其核心原理与技术要点。 一、系统架构 C#是一种面向对象的编程语言,适用于开发Windows平台的应用程序。在卡拉OK点歌系统中,C#被用来...

    upload(批量上传加水印功能)

    下面将详细讲解这两个功能的实现原理和技术要点。 批量上传功能: 批量上传的核心在于文件处理和多线程技术。在前端,通常会使用HTML5的FormData对象来收集用户选择的多张图片,并通过AJAX异步上传。用户在选择...

    摄像头捕捉行人调研情况.zip

    本文将深入探讨如何使用Java语言结合OpenCV库来实现这一功能,并提供相关的技术细节和实践要点。 首先,我们要了解Java作为编程语言在处理图像处理任务时的角色。Java具有丰富的库和API,可以方便地进行图像操作。...

    C语言实现图书管理系统

    总之,C语言实现的图书管理系统是一个结合了数据结构、文件操作、错误处理等多个知识点的综合性项目,它不仅能锻炼编程能力,也有助于理解和掌握计算机系统的工作原理。通过实际项目的实践,开发者能够更好地掌握...

    Win32汇编语言 全屏水滴效果(外文)

    创建全屏水滴效果涉及到多个技术要点: 1. **图形渲染**:在Windows中,这通常通过GDI(Graphics Device Interface)或更现代的DirectX API来实现。GDI是Windows操作系统内建的图形库,而DirectX则提供了更低级别的...

    c#文本词汇补全

    在描述中提到的“通过读取数据库内容,自动补全TEXTBOX内容”,我们可以理解为系统在后台有一个数据库,存储了大量的词汇或数据,当用户在TEXTBOX中键入字母时,程序会实时查询数据库,找出与已输入字符匹配的词汇,...

    code-review-checklist

    2. **命名规范**:检查变量、函数、类、文件等命名是否符合项目或团队的命名规范,名字应具有描述性,避免使用模糊或者难以理解的缩写。 3. **注释与文档**:确保关键的代码段有适当的注释,解释其功能、目的和工作...

    java开发手册[借鉴].pdf

    《Java开发手册》是一份详尽的编程...以上只是《Java开发手册》中的一部分要点,实际文档中可能包含更详细的规定和示例。遵循这些规约,可以确保代码的可读性、可维护性和团队合作的效率,从而提升软件项目的整体质量。

    JAVA三十个注意

    以上三十个注意事项涵盖了Java编程中的多个方面,从基本的命名规范到高级的架构设计,从代码优化到安全编程,都是程序员在日常工作中应当重视的要点。遵循这些指导原则,有助于编写出高质量、高可维护性的Java应用...

    VolumetricMethods-In-VisualEffects-2010.pdf

    课程特别注重有效的数据结构、着色架构、多线程/并行化、遮蔽处理和运动模糊等关键问题的处理方法。 课程的目标受众包括希望深入理解体积渲染技术的艺术家、有兴趣创建体积系统开发人员,以及希望了解体积渲染在...

    验证码识别程序-易语言

    在这个场景中,我们将探讨如何使用易语言来开发一个验证码识别程序,以及相关的技术要点。 验证码识别程序涉及到的主要知识点包括: 1. 图像处理:验证码识别的第一步是将图像数据转换为可处理的形式。易语言提供...

Global site tag (gtag.js) - Google Analytics