本文是关于JAVA多线程和并发的第一篇,主要说明基本概念,这是面试中最基本的要会的东西,如果这些都回答不了,基本上就没有机会了,本文从源码稍微深入一点去探讨常见的基本概念。本文并不会从最最最最基本的知识开始说起,将不费笔墨直击要害,所以需要一点多线程的基本知识才行,这也符合本博客的宗旨,即知识点再次提炼和升级。

一、进程和线程的区别

这一块详见 面试-进程与线程 里面的内容,相信已经够用了。

二、start()和run()方法的区别

以一个小例子入手,在主函数中尝试新建一个线程,并且以t.run()的形式去调用,从结果可以看出,java默认开启主线程来执行,当我们用t.run()去执行的时候,只是相当于简单的函数调用,因为从打印结果可以看出都是main进程,那么,实质上并没有新建一个子线程。

image

(注意,不是一调用就会去执行,而是说这个线程处于就绪状态,将有资格获得CPU的临幸,关于线程状态,后文会再次详细说明,关于start之后处于就绪状态这一点默认读者是清楚的,下面表述可能不会太顾及说明这一点):

image

那么,从表象上我们已经知道,run只是简单的函数调用,start才会真正地开启一个新线程来执行,下面从源码层面来看看start()的基本实现方式。

image

说明一下,本源码是基于JDK1.8,我们看到它的核心实现是一个native方法,IDEA上已经看不了,只好去看看openJDK了。

直接打开网址: Thread.c 我们可以看到:

image

我们看到很多关于线程的方法,但是这里是看不到具体的实现的,我们看到上面引入了jvm.h的库,所以实现应该是在jvm相关的代码中,直接点开: jvm.cpp 可以看到如下:

image

emmm,虽然不大看得懂,但是我们确实看到了start()会调用虚拟机去创建一个新的线程,最终再去调用run方法去执行。所以流程如下:

image

最终总结:

  • 调用start()方法会创建一个新的子线程并启动
  • run()方法只是thread的一个普通方法的调用

三、Thread和Runnable是什么关系

还是老规矩,先来翻翻源码:

我们可以看到,Thread是一个class,而Runnable是一个interface,而Runnable中只有一个抽象方法就是run().

image

那么,我们上面说到,新建一个线程是要靠start()来实现的,那么Runnable是如何来新建一个线程呢?它不是只有一个run()方法吗?

此时再来看Thread类,它里面有大量的方法,就包含了run()start()方法,它还有一个重要的构造函数为:

1
public Thread(Runnable target) {...}

就是说,传入Runable接口实例,再调用Threadstart()方法创建子线程,再来调用重写的run()方法就可以了。下面举个例子。

先说说用Thread的方式来创建一个子线程类:

image

这也从侧面证明了,线程是交替执行的,但是因为属于同一个进程,共享同一个地址和资源,所以不需要进行切换,极大提高了CPU执行效率。

下面再来看看Runnable接口是怎么实现多线程的:

image

总结一下他们俩:

  • Thread是一个类,Runnable是一个接口,前者实现后者
  • Threadstart方法,结合run()可以实现多线程,但是Runnable没有start()方法,所以要通过Thread()来实现,所以,两种方式最终都是通过Threadstart()来实现run()的多线程特性
  • 由于JAVA是单一继承的,所以推荐多使用Runnable接口