一、概述
为什么使用线程?从c开始,任何一门高级语言的默认执行顺序是“按照编写的代码的顺序执行”,日常开发过程中写的业务逻辑,但凡不涉及并发的,都是让一个任务顺序执行以确保得到想要的结果。但是,当你的任务需要处理的业务比较多时,且这些业务前后之间没有依赖(比如, a执行的过程中b也可以执行,b没有必要必须等待a执行完毕再去执行),那么此时,我们可以将一个任务拆分成多个小任务。
例如,任务a负责接收键盘的输入,b负责将一些参数及计算提前做好(假设计算量比较大),c负责将a的输入和b的结果做和。此时,abc顺序执行的话,如果a的输入被阻塞了即正在等待用户输入,b就无法执行,而此时cpu处于空置状态(假设是单cpu且单核),明显效率不高。
换一个思路,假如:abc分开成为三个任务,a的输入被阻塞了,那么此时就把b交给cpu去执行,待用户输入结果之后,b已经将计算结果输出给了c,此时,用户提交后,c便立即计算出了结果。
综上:多线程解决的是并发的问题,目的是使任务执行效率更高,实现前提是“阻塞”。它们看上去时同时在执行的,但实际上只是分时间片试用cpu而已。
二、java中的多线程
1.定义任务
任务:简单来说,就是一序列工作的集合,这些工作之间有前后顺序,这一系列过程执行过后将实现一个结果或达到一个目的。
首先,思考一个问题,为什么要定义任务?作为java程序员,我们不关心底层的多线程机制是如何执行的,只关心我写个怎样的任务,java的底层的多线程机制才能认识,才能调用你的任务去执行。java是定义了Runnable接口让你去实现,意思就是:你实现Runnable接口类定义一个类,该类的对象就是我能识别的任务,其他方式定义的程序,我都不会将它认为是任务。
好,到这里要明确一点,我们此时只谈论任务,不说多线程。任务和你平时在一个类中编写的代码并无区别,只是按照java的要求实现了一个接口,并在该接口的run方法中编写了你的代码。也就是说,你平时想编写一个类,该类能够完成一些功能,这个类里的任何方法、变量由你自己来定义,而编写任务时,你需要实现Runnable接口,把你想让该任务实现的代码写到run方法中,当然,你可以在你定义的任务类中再定义其他变量、方法以在run中调用。
2.代码实现
public class Task implements Runnable { protected int countDown = 10; private static int taskCount = 0 ; private final int id = taskCount++; public Task(){} public Task(int countDown){ this.countDown = countDown; } public String status(){ return "#"+id+"("+(countDown>0?countDown:"Task!")+"). "; } @Override public void run() { while(countDown-->0){ System.out.print(status()); Thread.yield(); } } }
注:此处代码源于《thinking in java》
定义了任务,此时并不涉及多线程,所以,任务本身就是一个类,它的对象我们可以在任意试用到的地方调用,例如:
public class TaskMain { public static void main(String[] args){ Task task = new Task(); task.run(); } }
就是在main中声明了该实例的对象,并调用了它的run方法,同我们平时创建类一样来调用对象的方法即可。
至此,一个任务定义完了。也就是说按照java的要求,我们实现了一个简单的任务。然而,实现任务的目的不只是为了实现任务,而是为了让多线程机制能够调用该任务去执行。请看:Java多线程——<二>将任务交给线程,线程声明