线程基本知识梳理
本文是关于JAVA多线程和并发的第一篇,主要说明基本概念,这是面试中最基本的要会的东西,如果这些都回答不了,基本上就没有机会了,本文从源码稍微深入一点去探讨常见的基本概念。本文并不会从最最最最基本的知识开始说起,将不费笔墨直击要害,所以需要一点多线程的基本知识才行,这也符合本博客的宗旨,即知识点再次提炼和升级。
一、进程和线程的区别
这一块详见 面试-进程与线程 里面的内容,相信已经够用了。
二、start()和run()方法的区别
以一个小例子入手,在主函数中尝试新建一个线程,并且以t.run()
的形式去调用,从结果可以看出,java默认开启主线程来执行,当我们用t.run()
去执行的时候,只是相当于简单的函数调用,因为从打印结果可以看出都是main
进程,那么,实质上并没有新建一个子线程。
(注意,不是一调用就会去执行,而是说这个线程处于就绪状态,将有资格获得CPU的临幸,关于线程状态,后文会再次详细说明,关于start之后处于就绪状态这一点默认读者是清楚的,下面表述可能不会太顾及说明这一点):
那么,从表象上我们已经知道,run
只是简单的函数调用,start
才会真正地开启一个新线程来执行,下面从源码层面来看看start()
的基本实现方式。
说明一下,本源码是基于JDK1.8,我们看到它的核心实现是一个native
方法,IDEA上已经看不了,只好去看看openJDK
了。
直接打开网址: Thread.c 我们可以看到:
我们看到很多关于线程的方法,但是这里是看不到具体的实现的,我们看到上面引入了jvm.h
的库,所以实现应该是在jvm
相关的代码中,直接点开: jvm.cpp 可以看到如下:
emmm,虽然不大看得懂,但是我们确实看到了start()
会调用虚拟机去创建一个新的线程,最终再去调用run
方法去执行。所以流程如下:
最终总结:
- 调用
start()
方法会创建一个新的子线程并启动 run()
方法只是thread
的一个普通方法的调用
三、Thread和Runnable是什么关系
还是老规矩,先来翻翻源码:
我们可以看到,Thread
是一个class
,而Runnable
是一个interface
,而Runnable
中只有一个抽象方法就是run()
.
那么,我们上面说到,新建一个线程是要靠start()
来实现的,那么Runnable
是如何来新建一个线程呢?它不是只有一个run()
方法吗?
此时再来看Thread
类,它里面有大量的方法,就包含了run()
和start()
方法,它还有一个重要的构造函数为:
1 | public Thread(Runnable target) {...} |
就是说,传入Runable
接口实例,再调用Thread
的start()
方法创建子线程,再来调用重写的run()
方法就可以了。下面举个例子。
先说说用Thread
的方式来创建一个子线程类:
这也从侧面证明了,线程是交替执行的,但是因为属于同一个进程,共享同一个地址和资源,所以不需要进行切换,极大提高了CPU执行效率。
下面再来看看Runnable
接口是怎么实现多线程的:
总结一下他们俩:
Thread
是一个类,Runnable
是一个接口,前者实现后者Thread
有start
方法,结合run()
可以实现多线程,但是Runnable
没有start()
方法,所以要通过Thread()
来实现,所以,两种方式最终都是通过Thread
的start()
来实现run()
的多线程特性- 由于JAVA是单一继承的,所以推荐多使用
Runnable
接口