用Thread实现多线程
随着CPU频率的增长出现停滞(目前处在4-5G的瓶颈),CPU转向多核方向发展。多线程作为实现软件并发执行的一个重要方法,也就具有越来越重要的地位。
Java在多线程处理方面性能超群、功能强大。而且Java语言进行多线程处理很简单,所以程序员利用多线程技术能编写出非常有效率的程序来充分利用CPU,使CPU的空闲时间能够保持在最低限度。
进程是程序的一次动态执行过程,它经历了从代码加载、执行到执行完毕的一个完整过程,这个过程也是进程本身从产生、发展到最终消亡的过程。多进程操作系统能同时运行多个进程(程序),由于CPU具备分时机制,所以每个进程都能循环获得自己的CPU时间片。由于CPU执行速度非常快,使得所有程序好象是在“同时”运行一样。
线程也称为轻量级进程,是程序执行的最小单元。一个标准的线程由线程ID,当前指令指针PC,寄存器集合和堆栈组成。一般一个进程由一个到多个线程组成,各线程之间共享程序的内存空间(包括代码段、数据段、堆等)及一些进程资源(如打开文件和信号)。一个经典的线程与进程的关系如图1所示。
file:///C:/Users/Sing/AppData/Local/Temp/msohtmlclip1/01/clip_image001.png
图1进程内的线程。
图片来源:操作系统概念第9版第四章第1节
或程序员的自我修养第一章第1.6节
本节先总结java创建线程的2种方法。
一、创建线程的第一种方法,通过继承Thread类实现多线程。
步骤:
1、定义一个“线程”类继承Thread类。
2、复写Thread类的run方法。
3、通过“线程”类的对象调用父类Thread的start()方法,来启动“线程”类的run()方法内的线程体。
格式:
class 线程类名 extends Thread /
{
属性
方法…
修饰符 run(){ // 复写Thread类里的run()方法
以线程处理的程序;
}
例如:用多线程实现买票小程序。
public class ThreadDemo {
public static void main(String[] args)
{
//定义了4个线程类的匿名对象,分别启动各自的线程
new TestThread().start();//
new TestThread().start();//
new TestThread().start();//
new TestThread().start();//
}
}
//“线程”类
class TestThread extends Thread {
private int tickets=5;//一共只卖5张票
public void run() {
while(true) {
if(tickets>0)
System.out.println(Thread.currentThread().getName()+"
出售票"+tickets--);
}
}
}
输出结果为:
Thread-0出售票5
Thread-0出售票4
Thread-3出售票5
Thread-2出售票5
Thread-2出售票4
Thread-1出售票5
Thread-1出售票4
Thread-2出售票3
Thread-3出售票4
Thread-0出售票3
Thread-3出售票3
Thread-2出售票2
Thread-1出售票3
Thread-2出售票1
Thread-3出售票2
Thread-3出售票1
Thread-0出售票2
Thread-0出售票1
Thread-1出售票2
Thread-1出售票1
由输出结果可以看出:
4个线程各自卖了5张票,一个卖了20张票,不符合设计要求。原因是这4个线程对象分别调用了各自的start方法,启动各自的run方法内的线程体,这四个线程对象各自拥有自己的资源,所以用继承Thread 类方法无法达到数据共享的目的。
可以通过static关键字把需要共享的数据静态化,让四个线程对象共享一个静态成员变量,例如把成员变量tickets静态化,把上例中的private int tickets=5;修改为
private static int tickets=20;
输出结果为:
Thread-0出售票20
Thread-0出售票16
Thread-3出售票17
Thread-2出售票18
Thread-1出售票19
Thread-2出售票13
Thread-3出售票14
Thread-0出售票15
Thread-3出售票10
Thread-2出售票11
Thread-1出售票12
Thread-2出售票7
Thread-3出售票8
Thread-0出售票9
Thread-3出售票4
Thread-2出售票5
Thread-2出售票1
Thread-1出售票6
Thread-3出售票2
Thread-0出售票3
由输出结果可看出4个线程共享一个静态变量tickets = 20,将同一数量的票分别卖出,符合设计要求。但实际工程中一般不在线程中使用静态变量,因为他的生命周期太长,相当于C语言的全局变量,一直占用内存空间(静态变量在内存的数据区分配),直到整个程序执行结束,静态变量才销毁。
当一个类继承 Thread 类之后,这个类的对象无论调用多少次 start()方法,结果都只有一个线程在运行,如下所示:
public class ThreadDemo
{
public static void main(String [] args)
{
TestThread t=new TestThread();//新建一个线程对象
// 一个线程对象只能启动一次,此处启动了4次。
t.start();//1
t.start();//2
t.start();//3
t.start();//4
}
}
字数有限,输出结果省略(可参考毕向东老师的视频教程),由毕老师视频可知,程序运行时出现异常,且只有1个线程的run方法被执行,其他3个线程都无法执行。用毕老师的话就是一个运动员在跑4x100m是,发令员只能在运动员跑第一圈时打响发令枪,而不能在第二三四圈时都打发令枪。
由上面的3个小实例可看出用继承Thread类的方法实现多线程时会有以下缺点:
1. 用Thread类无法达到资源共享的目的,
2. 用Thread类实现多线程时最好不用静态变量来实现数据共享。
3. 一个类继承Thread类后,无论这个类的对象调用多少次start()方法,结果都只有一个线程在运行。
最后总结一下start()方法和run()方法的区别。
当线程对象调用父类Theard类的start()方法后,start()方法告诉虚拟机的线程调度器,该对象内的run()方法处于就绪状态,此时run()方法并没有运行,一旦得到cpu时间片后,就开始执行run()方法,这里方法 run()称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。
Run()方法只是Thread类或Runnable接口的子类中的一个普通的方法而已,如果直接调用用Run方法,程序中依然只有主线程这一个线程,程序执行路径还是只有一条,还是要顺序执行,要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。
总结:调用start方法方可启动线程,而run方法只是thread或runnable的一个普通方法调用,还是在主线程里执行。
由java API可知Thread类实现Runnable接口,所以Thread类中的中的run方法是复写Runnable接口的run方法。所以个人感觉,当一个“线程”类继承Thread类时,复写Thread的run方法时,具相当于间接复写Runnable的run方法。如下图所示。
file:///C:/Users/Sing/AppData/Local/Temp/msohtmlclip1/01/clip_image002.png