• 并发编程(2) 之 线程驱动任务


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

    /**
     * 一个简单的任务类
     * @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();
               }
        }
    }



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

    2.线程的描述
       在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时间片分给不同的线程进行占用;而对于多处理器的环境,则线程调度器将会在多个处理器之间默默的分发线程;

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

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

  • 相关阅读:
    java乱码问题解决
    发送邮件工具方法
    MD5加密算法工具类
    2015年4月与5月
    Hark的数据结构与算法练习之煎饼排序
    Hark的数据结构与算法练习之图书馆排序
    Hark的数据结构与算法练习之耐心排序
    Hark的数据结构与算法练习之多路归并排序
    Hark的数据结构与算法练习之若领图排序ProxymapSort
    启动mysql出现了error the server quit without updating pid file (/var/lib/mysql/localhost.localdomain.pid)
  • 原文地址:https://www.cnblogs.com/chasewade/p/3372253.html
Copyright © 2020-2023  润新知