• Java并发操作——基本的线程机制


    1)定义任务

              线程可以驱动任务,因此需要一种描述任务的方式,这可以由Runnable接口来提供(有的地方比如Swing2中经常将其叫做可运行对象)。要想定义任务,只需要实现Runnable接口并编写run()方法,使得该任务执行命令。 但是此时呢,这个run()方法跟一个普通方法一样,并没有什么特殊的地方,不会产生任何内在的线程能力。要实现线程行为,我们必须在使用的时候显示的将一个任务附着到现场上。

    2)Thread类

              要想Runnable对象要想具有线程行为,传统的方式就是把它提交给一个Thread构造器。然后调用Thread类的start()方法为该线程执行必须的初始化操作,继而调用Runnable的run()方法。

          下面的代码简单演示Thread的用法

    public class LiftOff implements Runnable {
        
        public int countDown =10;
        
        private static int taskCount = 0;
        
        private final int id = taskCount++;
        
        public LiftOff() {
            System.out.println("默认构造函数");
        }
    
        public LiftOff(int countDown) {
            System.out.println("非默认的构造函数");
            this.countDown = countDown;
        }
        
        public String getStatus() {
            return "#" + id + "(" + (countDown > 0 ? countDown : "LiftOff") + ").";
        }
        
        @Override
        public void run() {
            while(countDown-->0) {
                System.out.print(getStatus());
                Thread.yield();
            }
        }
    
    }
    import java.util.concurrent.Executor;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    
    public class ThreadTestWithExecutor {
    
        private static final int count = 5;
        
        /**
         * @param args
         */
        public static void main(String[] args) {
            //1.什么都不加。
            for(int i=0;i<count;i++) {
               Thread t = new Thread(new LiftOff());
               t.start();
           }
            System.out.println("执行完毕!");
        }
    
    }

    第一次执行结果

    默认构造函数
    默认构造函数
    默认构造函数
    默认构造函数
    默认构造函数
    执行完毕!
    #0(9).#0(8).#0(7).#1(9).#3(9).#2(9).#4(9).#0(6).#2(8).#4(8).#0(5).#2(7).#4(7).#0(4).#2(6).#4(6).#3(8).#2(5).#4(5).#2(4).#2(3).#4(4).#1(8).#3(7).#2(2).#1(7).#3(6).#2(1).#1(6).#3(5).#0(3).#2(LiftOff).#1(5).#3(4).#4(3).#1(4).#3(3).#0(2).#4(2).#1(3).#3(2).#0(1).#3(1).#1(2).#4(1).#1(1).#3(LiftOff).#0(LiftOff).#1(LiftOff).#4(LiftOff)

    第二次执行结果

    默认构造函数
    默认构造函数
    默认构造函数
    默认构造函数
    默认构造函数
    执行完毕!
    #1(9).#3(9).#1(8).#3(8).#1(7).#3(7).#1(6).#3(6).#1(5).#3(5).#1(4).#3(4).#1(3).#3(3).#1(2).#3(2).#1(1).#3(1).#1(LiftOff).#3(LiftOff).#0(9).#2(9).#4(9).#0(8).#2(8).#4(8).#0(7).#2(7).#4(7).#0(6).#2(6).#4(6).#0(5).#2(5).#4(5).#0(4).#2(4).#4(4).#0(3).#2(3).#4(3).#0(2).#2(2).#4(2).#0(1).#2(1).#4(1).#0(LiftOff).#2(LiftOff).#4(LiftOff).

    从上述两次的执行结果中,我们可以得到以下结论:

         (1)多线程的执行不是按照顺序执行的,各个线程之间相互交叉。

             #0(9).#0(8).#0(7).#1(9).#3(9).#2(9) 说明可Runnable0还没执行完,Runnable2 就执行了。

         (2)start()方法看起来是产生了一个对长期运行方法的调用(即run方法),但是从实际的输出情况来看,start()方法迅速的返回了,因为“执行完毕”在run()方法执行完毕之前就已经输出了。实际上,我们产生的是对Runnable.run()方法的调用,并且这个方法还没有完成,但是因为这个方法是由不同的线程执行的,因此我们仍然可以执行main()线程中的其他操作(当然这种能力并不局限于main()线程,任何线程都可以启动另一个线程)。因此这时候程序会同时运行两个方法、

        (3)比较两次执行结果可以发现,两次执行的结果不一样。这是因为线程调度机制是非确定的。

     3)使用Exexutor

             在Java SE5的java.util.concurrent包中的执行器(Executor)将为我们管理Thread对象,从而简化了并发编程。

         下面的代码就列举了几种不同Executor的执行情况:

    public class LiftOff implements Runnable {
        
        public int countDown =10;
        
        private static int taskCount = 0;
        
        private final int id = taskCount++;
        
        public LiftOff() {
            System.out.println("默认构造函数");
        }
    
        public LiftOff(int countDown) {
            System.out.println("非默认的构造函数");
            this.countDown = countDown;
        }
        
        public String getStatus() {
            return "#" + Thread.currentThread().getId() + "(" + (countDown > 0 ? countDown : "LiftOff") + ").";
        }
        
        @Override
        public void run() {
            while(countDown-->0) {
                System.out.print(getStatus());
                Thread.yield();
            }
        }
    
    }
    import java.util.concurrent.Executor;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
    * CachedThreadPool
    */
    public class ThreadTestWithExecutor { private static final int count = 5; /** * @param args */ public static void main(String[] args) { ExecutorService exec = Executors.newCachedThreadPool(); for(int i=0;i<count;i++) { exec.execute(new LiftOff()); } exec.shutdown();

              //3.使用FixThreadPool
                ExecutorService exec2 = Executors.newFixedThreadPool(3);
                for(int i=0;i<count;i++) {
                     exec2.execute(new LiftOff());
                  }
               exec2.shutdown();

              //4.SingleThreadExecutor
               ExecutorService exec3 = Executors.newSingleThreadExecutor();
               for(int i=0;i<count;i++) {
                  exec3.execute(new LiftOff());
                  }
                exec.shutdown();

            System.out.println("执行完毕!");   
    
        }
    
    }

    这几段代码,都很简单,但是有几点需要说明

        1)shutdown()方法的调用可以防止新任务被提交给这个Executor,当前线程(在上述代码中就是驱动main()的线程)将继续运行在shutdown()被调用之前提交的所有任务。

    换句说,虽然是shutdown了,但是当前在执行器中的线程仍然会继续执行。

        2)FixedThreadPool可以一次性预先执行代价高昂的线程分配(因而就可以限制线程的数量了)。这样会节省时间,因为你不用为每个任务都固定的付出创建线程的时间(记住,虽然每次new了一个Runnable对象,但是并不是每个Runnable对象都会new一次Thread.),直接从线程池中取线程即可。 但是,我们一定要记住一点,在任何线程池中,现有线程在可能的情况下, 都会被复用。

       3)SingleThreadExecutor就像是线程数量为1的FixedThreadPool。这时候如果向其提交了多个任务,这些任务就会排队。每个任务都会在下一个任务开始之前运行结束。所有的任务将使用相同的线程

  • 相关阅读:
    「持续集成实践系列」Jenkins 2.x 搭建CI需要掌握的硬核要点
    常用的20个在线工具类网站清单
    推荐一款Python神器,5 行 Python 代码 实现一键批量扣图
    一套测试用例如何实现支持多个环境运行
    推荐一款Python数据可视化神器
    全网独家:成长经历分享 & 我为什么要写书?
    整理一份程序员常用的各类工具、技术站点
    关于《自动化测试实战宝典:Robot Framework + Python从小工到专家》
    重磅新书 |《自动化测试实战宝典:Robot Framework + Python从小工到专家》上市了!
    献给即将35岁的初学者,焦虑 or 出路?
  • 原文地址:https://www.cnblogs.com/chenfei0801/p/3006480.html
Copyright © 2020-2023  润新知