写这篇文章是来简单说一下java多线程的理解,看网上很多多线程的讲解讲的云里雾里,不说人话,属于不会的看不懂会的不用看系列,害人不浅。这里用大白话简单讲一下多线程里一些重要的东西。
程序本质上就是一个函数,或者说有唯一一个函数作为入口,像我们平时写的程序,那个main函数就是函数的入口。程序中需要做什么任务就需要调用什么函数或者写相关代码,比如:
public static void main(String[] args) { doA(); doB(); }
但是这两个是顺序执行,先做A再做B,如果我们想让这两个同时执行,那么就需要创建两个线程来帮我们做,java中创建线程就是继承Thread()或者实现Runnable()接口,然后重写或实现run()方法,线程启动就是start()函数,start()不是用java实现,我们不用管它,我们关注的就是这个线程创建后就是为了执行run()方法,执行完就没了。为了方便理解,可以把这个run()方法看做我们平时写的main()方法作为函数入口是一个性质。
那么这里还有一个Callable接口,我们知道,实现了这个接口的call()方法就可以得到返回值,那我们看到run()方法返回值是void,那这个是怎么得到返回值的呢?不要想得很高大上,我们知道Callable得配合线程池或者FutureTask类来使用,好像没看到Thread或者Runnable,但其实FutureTask已经实现了Runnable,来看他的继承和实现:
FutureTask实现了run()方法:
public void run() { result = callable.call(); }
上面的代码简写了,但本质就是去用run()方法执行call()函数,然后保存下来结果值,源码中是调用set方法保存结果到outcome变量中,然后后续操作在把他取出来(细节方面不是本文重点,自己再去研究)。
还有一个就是线程池,线程池中有个重点问题就是能够比我们执行快、能重复利用,有些人不理解线程池执行和我们创建后start()有啥区别,再加上有些教程或者网课把这个奉为神迹一样,云里雾里地去讲,很容易让我们往难了想,难以理解。其实他本质上和前面Callable的差不多。下面先画个图说一下线程值大小为1的时候:
上方是线程池中的那个线程,姑且叫员工小A吧,下方是任务队列,线程和任务都是实现了Runnable的类,都有run()方法。
你以为的线程池执行:小A从任务队列中不断获取任务,然后像文章开头讲的那样调用start()创建线程执行。
class 小A{ public void run() { while(thread = getTask()){ thread.start(); } } }
实际上的线程池执行:小A从任务队列中不断获取任务,不断的去调用任务的run()方法。
class 小A{ public void run() { while(thread = getTask()){ thread.run(); } } }
注意了,这里的任务类虽然是实现了Runnable,但不能像文章开头说的那样把run()看做函数入口了,run就是个单纯的函数,如果改改源码不让他实现Runnable不叫run也是没问题的。再看第一种方式的话会创建四个线程,而实际上只创建了一个不断顺序调用run()方法而已,所以重复利用,没有多次创建开销,比较快,没啥高大上的。