
1、理解线程(Thread)
在理解线程之前,先要理解什么是进程(Process)。我们现在所使用的操作系统,比如Windows,Linux等等,都是多任务操作系统。什么叫多任务呢。就是说在一个操作系统中,可以同时运行许多不同的任务。每个任务相当于一个程序,或者说是一个进程,比如我们在windows下我们可以同时打开播放器听歌,聊QQ,浏览网页等等,这些程序在我们看来好像都是同时运行的。但是实际情况并非如此,在单一CPU的计算机上,cpu只是将时间切割为时间片,然后将时间片分配给这些程序,获得时间片的程序开始执行,不等执行完毕,下个程序又获得时间片开始执行,这样多个程序轮流执行一段时间,由于现在cpu的高速计算能力,给人的感觉就像是多个程序在同时执行一样。这个就是多任务分时的概念。
一般可以在同一时间内执行多个程序的操作系统都有进程的概念.一个进程就是一个执行中的程序,而每一个进程都有自己独立的一块内存空间,一组系统资源.在进程概念中,每一个进程的内部数据和状态都是完全独立的.因此可以想像创建并执行一个进程的系统开像是比较大的,所以线程出现了。在java中,程序通过流控制来执行程序流,程序中单个顺序的流控制称为线程,多线程则指的是在单个程序中可以同时运行多个不同的线程,执行不同的任务.多线程意味着一个程序的多行语句可以看上去几乎在同一时间内同时运行.
线程和进程很相似,一个程序运行时,CPU为这个程序的不同线程划分出多个时间片,在这个时间片上轮流进行执行,比如这个程序中第一个线程执行了一段时间,还没等这个线程执行完毕CPU又跳到下个进程执行,如果此时刚好这个线程所在的进程拥有的时间到了,那么CPU就会跳到另外的进程中执行,过段时间又回到刚才那个进程开始执行,就这样不停的轮回。当然这些过程都是CPU高速执行的,我们所能感觉到的就是进程与进程之间,或者线程与线程之间都是同时运行的。
也正因为如此,多线程能让程序看上去更漂亮,执行速度更快。试想一下,如果所有的程序都是单线程,那么用户的所有操作必须是依次执行的,每执行一个操作,都必须要等这个操作执行完毕才能进行下一个操作,如果这个操作需要大量的计算,那么用户就必须耐心的等待,直到操作完成。如果想在中途取消这个操作也是不可能的,因为程序在执行一个操作的同时不可能再有空去执行另外一个操作了。但是如果加入了多线程,那么这个就成为可能,1个线程去执行计算的操作,1个线程去监听的用户的点击事件。虽然这两个线程仍然是按次序执行的,但由于分时的特性,在我们看来就象是同时在执行。如果在多处理器的计算机上,代码不必知道这个程序是运行在一个处理器还是多个处理器上,多个线程可由不同的处理器来运行,这样大大提高了程序运行的效率。
2、线程的创建
多线程在不同的程序语言中差异不大,只要理解了的线程了概念,那么在不同的语言中使用多线程仅仅是语法上不同而已。在java中我们可以使用两种不同的方式来创建线程。即Thread类和Runnable接口。下面分别来看看这两种用法例子:
(1)Thread类
class BasicThread extends Thread{
BasicThread(String name){
super(name);
start();
}
public void run(){
for (int i = 1; i < 5; i++)
{
System.out.println(this.getName() + " " + i + "#");
}
}
public static void main(String [] agrs) {
for (int i = 1; i < 5; i++)
{
Thread myThread = new BasicThread("第" + i + " 个线程);
}
}
}
使用一个类继承Thread类,其中必须覆盖Thread类的run方法,这个run方法里就线程所要执行的内容。通过Thread类的start()方法来启动线程。一般创建线程有三个步骤:
I、创建Thread类或者Thread类的子类的实例。
II、覆盖run方法
III、使用Thread类的方法start()启动线程。
在上面的程序中,有5个线程,主线程main方法,任何程序都会有个主线程,而main方法就是主线程的入口点。其余4个就是我们创建的用户线程。这种继承自Thread类的方法并不是太好,因为java中只允许单根继承,因此如果继承了Thread类后就不能再继承其他类。那么我们还可以通过内部类的方式来创建线程。
class BasicThread{
public static void main(String [] agrs) {
for (int i = 1; i < 5; i++)
{
Thread myThread = new Thread("第" + i + " 个线程){
public void run() {
for (int i = 1; i < 5; i++)
{
System.out.println(this.getName() + " " + i + "#");
}
}
};
myThread.start();
}
}
}
上面是创建了一个继承于Thread的匿名内部类,通过new将其向上转型为Thread类型。这个匿名内部类重写了run方法,作用和上面那个小程序是一样的。当然我们也可以将其写成内部类的方式。但是如果我们只用到Thread的创建线程的功能,那么就没必要单独写成一个类,匿名内部类的方式就比较合适。
(2)Runnable接口
如果觉得内部类的方式会使程序不易阅读,而且又不想继承于Thread类,我们还可以使用Runnable接口,那么我们就可以让这个类干些其他的事情了。
class BasicThread implements Runnable{
Thread thread;
BasicThread(String name){
this.thread = new Thread(this,name);
thread.start();
}
public void run() {
for (int i = 1; i < 5; i++)
{
System.out.println(thread.getName() + " " + i + "#");
}
}
public static void main(String [] args) {
for (int i = 1; i < 5; i++)
{
Runnable thread = new BasicThread("第" + i + " 个线程);
}
}
}
我们可以看到,实现Runnable接口仅仅只需要覆盖run方法,而创建线程依然需要使用到Thread类,并将实现了Runnable接口的类作为Thread构造器的第1个参数,这样才能创建线程。如果要使用到诸如start()、join()、sleep()等等方法,还是需要Thread类的对象来使用,Runnable本身并不具备这些方法。Thread类和Runnable接口之间如何选择,还是要根据具体情况而定。
3、休眠(sleep)
先来看看上面程序在我的机子上运行的一次结果:
第 2 个线程 1#
第 2 个线程 2#
第 2 个线程 3#
第 2 个线程 4#
第 1 个线程 1#
第 1 个线程 2#
第 1 个线程 3#
第 1 个线程 4#
第 3 个线程 1#
第 3 个线程 2#
第 3 个线程 3#
第 3 个线程 4#
第 4 个线程 1#
第 4 个线程 2#
第 4 个线程 3#
第 4 个线程 4#
这个结果可能会让你感到意外,至少刚开始我就会这样。为什么第2个线程会抢在第1个线程之前呢。这个就是CPU分配时间的不确定性,你永远也不知道线程的运行次序是怎么样的。因此在编写多线程的时候,一般不用考虑执行的次序。下面我看看加入休眠之后情况又会怎样。
class BasicThread implements Runnable{
Thread thread;
BasicThread(String name){
this.thread = new Thread(this,name);
thread.start();
}
public void run() {
for (int i = 1; i < 5; i++)
{
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
System.out.println("throw InterruptedException!");
}
System.out.println(thread.getName() + " " + i + "#");
}
}
public static void main(String [] args) {
for (int i = 1; i < 5; i++)
{
Runnable thread = new BasicThread("第 " + i + " 个线程");
}
}
}
sleep(long ms)这个是Thread类的一个静态方法,参数是long型,表示毫秒数,当程序运行到这句代码时,那么当前线程将被挂起,直到多少毫秒过后继续执行该线程。
注意,使用 sleep后表示该线程被挂起,但该线程并没有结束,如果一个线程被挂起后,在这个线程又使用interrupt()中断线程,那么就会抛出InterruptedException异常,下面来看看这个程序的运行结果:
第 1 个线程 1#
第 2 个线程 1#
第 3 个线程 1#
第 4 个线程 1#
第 1 个线程 2#
第 2 个线程 2#
第 4 个线程 2#
第 3 个线程 2#
第 1 个线程 3#
第 2 个线程 3#
第 3 个线程 3#
第 4 个线程 3#
第 1 个线程 4#
第 2 个线程 4#
第 4 个线程 4#
第 3 个线程 4#
当使用了休眠后,线程的执行次序依然是不确定的。
4、加入到某个线程(join)
将某个线程加入到另外一个线程中,表示直到另外一个线程结束后,当前线程才会继续运行。再将上面那个程序修改一下:
class BasicThread implements Runnable{
Thread thread;
BasicThread(String name){
this.thread = new Thread(this,name);
thread.start();
}
public void run() {
for (int i = 1; i < 5; i++)
{
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
System.out.println("throw InterruptedException!");
}
System.out.println(thread.getName() + " " + i + "#");
}
}
public static void main(String [] args) {
for (int i = 1; i < 5; i++)
{
BasicThread thread = new BasicThread("第 " + i + " 个线程");
try
{
thread.thread.join();
}
catch (InterruptedException e)
{
System.out.println("throw InterruptedException!");
}
}
}
}
下面是程序的运行结果:
第 1 个线程 1#
第 1 个线程 2#
第 1 个线程 3#
第 1 个线程 4#
第 2 个线程 1#
第