并发编程 二 之 线程驱动任务

前序:
我们前面说了,我们的程序是可以分成若干个程序片段的,而每一个程序片段我们都可以用来做为一在多个任务;当然做为独立可运行的任务将更为理想化;在多线程来实现并发的手段时,我们常常将没一个任务用一个独立的线程来进行驱动;

下面是一个简单的线程来驱动我们的一个任务的执行;
[b]1.任务的描述[/b]

/**
* 一个简单的任务类
* @author liuwei 继承于Runnable接口,并提供Run方法的实现
* Runnable为描述任务的方式
*/
public class SimpleTask implements Runnable {
/**
* 任务执行总数
*/
protected int countDown = 10;

private static int taskCount = 0;

/**
* 任务执行次数id
*/
private final int id = taskCount++;

/**
* 无参构造函数
*/
public SimpleTask() {
}
/**
* 带参构造函数
* @param countDown
*/
public SimpleTask(int countDown) {
this.countDown = countDown;
}
/**
* 显示任务的信息
* @return
*/
public String status() {
return "#" + id + "(" + (countDown > 0? String.valueOf
(countDown):"SimpleTask!")+ "), ";
}

/**
* 任务的执行方法
*/
public void run() {
while (countDown-- > 0) {
System.out.print(status());
/**
* 对线程调度器的一种建议,即java线程机制的一部分,
* 可以将cpu从一个线程转到另一个线程,且类似告诉
* 别人,我这里目前不需要占用现在的内存资源了;
* 我们可以说它是一个上下文切换的一个动作;
*/
Thread.yield();
}
}
}


这个单独的任务,并不能够独立的执行;且也不具有产生任务内在的线程能力;而且,要实现线程行为,还必须显式的将这个任务附到线程上去;

[b]2.线程的描述[/b]
在java中,将Runnable对象转变成工作任务的传统方式就是将它将给线程,并由线程来驱动它执行;这一点和Quartz里的Job与Scheduler有点相似;即作业不能够自己运行,它需要在Scheduler上进行注册后,由任务调度器来负责管理它;
下面我们来实现一个线程驱动任务的执行;

/**
* 一个线程来驱动任务的执行
* @author liuwei
*/
public class TreadDriverTask {

/**
* 当通过线程thread的start方法调用任务的run方法的时候,
* main函数所对应的线程将继续执行System的语句;它不会等待
* thread执行完毕后再执行System操作;
* 我们前面说过,一个线程就是在进程中有一个单一的顺序控制流;
* @param args
*/
public static void main(String[] args){
Thread thread=new Thread(new SimpleTask());
thread.start();
System.out.println("---->main执行的现成与线程thread是相互独立
的!");
}
}

它的执行情况为:
---->main执行的现成与线程thread是相互独立的!
#0(9), #0(8), #0(7), #0(6), #0(5), #0(4), #0(3), #0(2), #0(1), #0(SimpleTask!),

我们来看看多个现在执行多个任务的时候,它是一个什么样的情况!

/**
* 多个线程来驱动任务的执行
* @author liuwei
*/
public class MultThreadDriverTask {

/**
* 当通过线程thread的start方法调用任务的run方法的时候,
* main函数所对应的线程将继续执行System的语句;它不会等待
* thread执行完毕后再执行System操作;
* 我们前面说过,一个线程就是在进程中有一个单一的顺序控制流;
* @param args
*/
public static void main(String[] args){
for(int i=0;i<5;i++){
new Thread(new SimpleTask()).start();
}
System.out.println("---->waiting for ......!");
}
}

它的执行效果如下:
#0(9), #1(9), ---->waiting for ......!
#0(8), #3(9), #1(8), #3(8), #2(9), #1(7), #4(9), #0(7), #2(8), #4(8), #0(6), #2(7), #4(7), #0(5), #2(6), #4(6), #0(4), #2(5), #4(5), #0(3), #2(4), #4(4), #0(2), #2(3), #4(3), #0(1), #3(7), #2(2), #4(2), #1(6), #0(SimpleTask!), #3(6), #2(1), #1(5), #4(1), #3(5), #2(SimpleTask!), #1(4), #4(SimpleTask!), #3(4), #1(3), #3(3), #1(2), #3(2), #1(1), #3(1), #1(SimpleTask!), #3(SimpleTask!),
我们可以看出:
当通过for循环分别创建5个线程的时候,在创建的过程中是有一定的时间差的;比如我在创建第5个线程的时候,可能第一个创建的线程已经开始执行附属任务的run方法;且所有线程创建完毕后(这个是由main所在线程内完成的),则执行了System操作;而前面创建的5个线程,他们将自己运行自己的;
另一个特点是,他们的输出是相互混乱的;因为线程间的相互切换是由线程调度器来进行自动控制的;对于单处理器的环境,则调度器将将cpu时间片分给不同的线程进行占用;而对于多处理器的环境,则线程调度器将会在多个处理器之间默默的分发线程;

[b]3.线程调度机制的非确定性[/b]
对于上面的结果,可能每一次运行的输出也是不一样的;因为这就是线程调度机制的非确定性,同样,对于不同的jdk版本,其运行的输入也不一样;首先对于同一jdk版本下的运行结果的不一致,是因为对于每一次的运行,它的cpu时间片的分配可能进行不同的分配(单处理器环境),它的任务处理器也可能每次分配的线程也不一致(多处理器环境);对于不同jdk版本,在sun的早期jdk,cpu时间的切片动作不是很频繁,可能上面的5个线程,线程1执行完后,线程2才会执行;它启动所有线程的代价相对现有jdk的启动可能代价更高;现在的jdk,时间切片行为更为完善,每个线程看起来都会获得更加正规的服务(处理器服务于它);

[b]4.main函数为何没有对启动的线程进行异常捕获且没有引用的Thread没有被垃圾回收[/b]
在上面的main方法里创建Thread对象的时候,它并没有去捕获这些对象的引用;通常大家都知道,对于一个对象没有引用指向他,则它将被垃圾回收器定时回收;但对于thread就不同了,每个thread都注册了自己,因为确实有一个引用指向它,且只有当run方法执行完毕后,垃圾回收器才会清楚它;且我们说过了mian方法,与启动的每一个thread是相互独立的,我们不能够在A线程1去捕获B线程下可能存在的异常;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值