多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
## 为什么要多线程 为什么要多线程啊?操作系统有多进程管理,那多线程感觉没有必要。 所谓线程,是程序代码的执行,一个进程至少得一个线程,不然进程如何执行。 比如说word,如果这个进程没有多线程,如果有个定时保存文档的功能,当自动保存的时候,还能继续输入文字吗?当然是不能的。因为单线程只能干一件事,无法并发和并性,直接导致用户体验不好。CPU的快速运算能力还有多核就被浪费了。 那么为什么不能用多进程来处理呢?一个进程来接受用户输入的文字,另一个进程来自动保存。但是我们需要知道,进程之间是相互隔离的,他们要共享数据,是非常麻烦的。比如说要共享被编辑的文件内容并不容易。 可以说,进程是拥有资源的基本单位,线程是CPU调度的基本单位。 比如有两个进程,word和QQ。 Word进程打开文件,这是它的资源,QQ打开了Socket,这也是它的资源。 假设word有两个线程,T1负责用户文字输入,T2负责自动保存 QQ也有两个线程,T3负责从Socket读取数据,T4负责对音乐数据进行解码。 操作系统在做调度的时候,基本单位不是word,QQ,而是T1,T2,T3这些线程。 ![](http://p8a6vmhkm.bkt.clouddn.com/picgo20180824095135.png?picgo) 每个线程执行的都是进程代码中的某个片段。 我们知道Java中总是讨论多线程编程,但是从来没有提过Java多进程,为什么呢? 因为java是一运行在JVM中,对于操作系统来说,JVM就是java.exe运行起来,也就是个进程。 所以说JVM这个进程其实就是他们的容器。 另外Python等动态语言也有虚拟机,也可以进行多线程编程了。 所以说虚拟机是个好东西,程序员不需要再去操作内存,可以屏蔽操作系统的差异,让写的程序可以任意在支持该语言虚拟机上的操作系统上运行。 为什么在Java中创建的Thread对象需要调用start方法才能启动线程,而不是直接调用run呢?如果直接调用run的话,只是用当前线程去执行一个普通的函数,根本没有新线程创建出来。 所以,想创建一个新的线程出来,必须有准备工作,比如设置好线程的上下文,比如栈(相当于函数调用)、线程状态,比如线程的PC(Program Counter)等,线程才可以被调度,一旦被调度,就会执行run()方法了。 ## 线程池 既然线程是属于进程的,那么创建一个线程应该很轻松啊,为什么要有线程池这个东西呢? 因为线程虽然轻量,但是对与互联网应用,每个用户的请求都创建一个线程,那会非常多。而且众多的线程去竞争CPU,不断的切换,也会让CPU调度不堪重负,很多线程不得不等待。 所以最好的方法用少量的线程,并且让这些线程尽可能充分利用。也就是说只创建一定数量的线程,让这些线程来处理所有的任务,任务执行完了以后,线程不结束,而是回到线程池里面,等待接受下一个任务。 ![](http://p8a6vmhkm.bkt.clouddn.com/picgo20180824100039.png?picgo) 这些线程可以预先创建,任务来就不用临时再创建了,可以立刻开始服务。 但是线程是程序代码的执行,是个动态的东西,怎么可以预先创建呢? 这是因为线程是有状态的,当线程池的线程刚刚创建的时候,会让他们进入阻塞状态:等待任务的到来。如果任务来了,就唤醒其中的线程,让它拿到任务去执行。 那如何让线程进入阻塞状态呢? 就是一个线程调用take()方法取数据的时候,如果这个Queue中没有数据,该线程就会阻塞,同样一个线程调用它的put方法放数据的时候,如果Queue满了,也会阻塞。 ![](http://p8a6vmhkm.bkt.clouddn.com/picgo20180824100323.png?picgo) 所以说线程池的每个线程的run()方法中,要设置一个循环,每次都尝试从BlockingQueue获取任务,如果Queue是空的,就阻塞等待,如果有任务来了,就会通知到线程吃的某个线程去处理,橱窗完了,试图从BlockingQueue中获取任务,依次循环下去。 ~~~ 线程池中的Worker线程: public class WorkerThread extends Thread { private BlockingQueue<Task> taskQueue = null; private boolean isStopped = false; //持有一个BlockingQueue的实例 public WorkerThread(BlockingQueue<Task> queue){ taskQueue = queue; } public void run(){ while(!isStopped()){ try{ Task task = taskQueue.take(); task.execute(); } catch(Exception e){ //log or otherwise report exception, //but keep pool thread alive. } } } ......略...... } ~~~ 这套代码已经被吸收到JDK,作为java.util.concurrent包的一部分。