JAVA多线程
1. 引言
并发编程使我们可以将程序划分为多个分离的、独立的任务,通过使用多线程机制,这些独立任务重的每一个都将由执行线程来驱动。一个线程就是在进程中的一个单一的顺序控制流,因此单个进程可以拥有多个并发执行的任务。线程模型为编程带来了遍历,简化了在单一程序中同时交织在一起的多个操作的处理。在使用线程时,CPU将轮流给每个任务分配其占用时间。因此,多任务和多线程往往是使用多处理器系统的最合理方式。
2. 定义任务
线程可以驱动任务,因此我们需要一种描述任务的方式,这种方式可以由Runnable接口提供。定义任务只需要实现Runnable接口并编写run()方法,使得该任务可以执行所需要的命令,例如:
class Taskclass implements Runnable{
public Taskclass(){}//构造函数
public void run(){}//重写run方法
}
当我们创建好了实现Runnable接口的类之后,我们便可以通过start函数运行指定的任务,例如:
public class threadTest{
public static void main(String[] args){
Taskclass task=new Taskclass();//实例化任务类
Thread thread=new thread(task);//创建执行指定任务的线程
thread.start();//开始执行线程
}
}
任务中run()方法指明了如何完成这个任务,Java虚拟机会在线程执行中自动调用这个方法,因此我们无需特意调用它。直接调用run()知识在同一个线程中执行该方法,而没有新线程被启用。
3. Thread类
将Runnable对象转变为工作任务的传统方式是把它提交给一个Thread构造器,前文中的代码给出了如何使用Tread来驱动任务类。Thread构造器只需要一个Runnable对象,即public Thread(Runnable object)。调用Thread对象的start()方法为该线程执行必须的初始化操作,然后调用Runnable的run()方法,在这个新线程中启动该任务。
Thread类中还包括了一些常用的操作线程的方法包括,sleep(long mills)使线程睡眠指定时间;yield()使线程暂定并执行允许执行其他线程;join()等待线程结束。下面给出具体的应用示范。
public class threadTest{
Runnable task1=new printChar('a',100);
Runnable task2=new pirntChar('b',100);
Runnable task3=new printNum(100);
//创建任务类
Thread thread1=new Thread(task1);
Thread thread2=new Thread(task2);
Thread thread3=new Thread(task3);
//创建线程
thread1.start();
thread2.start();
thread3.start();
//启动线程
}
class printChar implements Runnable{
char c;
int num;
public printChar(char c, int num){
this.c=c;
this.num=num;
}
public void run(){
for(int i=0;i<num;i++)
System.out.println(c);
}
}
class printNum inplements Runnable{
int num;
public printNun(int num){
this.num=num;
}
public void run(){
for(int i=0;i<num;i++)
System.out.println(i);
}
}
上述任务将在三条线程中输出字母a,b和顺序打印0-99数字,若对上述程序进行以下修改,则结果将会产生变化,例如在printNum类中修改run()方法,添加thread.yield()方法
public void run(){
for(int i=0;i<num;i++){
System.out.println(c);
thread.yield();
}
}
则输出则会变为,每打印一个数字后都会暂停printNum任务,因此最终结果来看每个数字后面都会紧跟一系列的字符,因为在printNum线程暂停时,打印字符的任务允许运行。
继续修改run()方法,在其中添加join()方法,例如:
public void run(){
Thread thread4=new Thread(thread4.start(new printChar('c', 40)));
try{
for(int i=0;i<num;i++){
System.out.println(i);
if(i==50) thread4.join();//当i为50时,暂定当前线程跳入thread4线程
}
}catch(InterruptedException ex){
ex.printStackTrace();
}
}
在run方法中创建了一个新线程thread4,在i的值上升为50时跳转至thread4,线程4打印字符40次。我们再次对run方法进行修改,添加了sleep方法,代码如下:
public void run(){
try{
for(int i=0;i<num;i++){
System.out.println(c);
if(i>=50) Thread.sleep(1);//当i大于等于50时,线程每输出一次字符则睡眠1毫秒
}
}catch(InterruptedException ex){}
}
4. 小结
当main()创建Thread对象时,它并没有捕获任何对这些对象的引用。在使用普通对象时,这对于垃圾回收来说是公平的,但是在使用thread时,情况则不同。每个thread都“注册”了自己,因此确实有一个对他的引用,而且在它的任务退出运行run()方法并死亡之前,我们无法清除它。因此,一个线程会创建一个单独的执行线程,在对start()调用完成之后,这个线程任然会存在。