zoukankan      html  css  js  c++  java
  • Java多线程学习之任务的创建以及在线程中执行任务

    传统的创建任务、驱动任务的方式

    1.继承Thread类

      通过继承Thead类,并重写run方法,在run方法里面编码具体的任务,调用对象的start方法驱动任务。

      

    public class TestThread extends Thread{
    
        private int count = 5;
      //创建介绍String形参的构造器,一般参数作为任务的名称,以区分不同的线程
        public TestThread(String name) {
            super(name);
        }
      //run方法体,就是要执行的任务
        public void run() {
            while (true) {
                System.out.println(this);
                if(--count == 0)
                    break;
            }
        }
        //重写toString方法实现可读性更好的线程打印
        public String toString() {
            return getName()+"   count="+count;
        }
    
        public static void main(String[] args) {
         //创建5个任务对象,并且调用start方法让线程执行任务;
    for(int i=0;i<5;i++) { new TestThread("#Thread "+i).start(); }
         //这里往下都是当前线程(即是执行main函数这个线程),而自定义的TestThread任务都是在main线程中开启新的线程来驱动 } }
    输出:

    #Thread 0 count=5
    #Thread 3 count=5
    #Thread 1 count=5
    #Thread 2 count=5
    #Thread 1 count=4
    #Thread 3 count=4
    #Thread 3 count=3
    #Thread 0 count=4
    #Thread 3 count=2
    #Thread 1 count=3

    ...

    #Thread 0 count=1
    #Thread 4 count=3
    #Thread 4 count=2
    #Thread 2 count=2
    #Thread 4 count=1
    #Thread 2 count=1

      从log可以看出,这些任务被调动的机会很随机,这取决于线程调度器,而调度器的调度机制又取决于运行的操作系统,即是在同一个平台上,运行多次每次都会出现不一致的结果。

    2.实现Runnable接口

      这种方式与继承Thread类的方式类似,任务类实现Runnable接口的run方法,但是这个任务只是被定义了,并没有产生线程的行为,所以还必须把任务类作为创建Thread对象的构造器形参,并通过调用Thread实例的start方法,使任务附着到线程中。

    public class LiftOff implements Runnable{
    
        private int count=4;
    
        private static int taskId = 0;
    
        private int id = taskId++;
    
        private String getStatus() {
            return "task"+id+"("+count+")";
        }
      //定义任务
        public void run() {
            while (count-->0) {
                System.out.println(getStatus());
            }
        }
    
        public static void main(String[] args) {
            for(int i=0;i<3;i++) {
            //将任务对象实例通过构造器传递给Thread实例,使任务附着到线程上得到执行
    new Thread(new LiftOff()).start(); } } }
    输出:

    task1(3)
    task2(3)
    task0(3)
    task2(2)
    task1(2)
    task2(1)
    task0(2)
    task2(0)
    task1(1)
    task0(1)
    task1(0)
    task0(0)

      同继承Thread类执行线程一样,任务都是被“随机”执行。不过,这种方式下,任务与线程明确分开在不同的对象,架构比前一种要清晰。

    使用Executor

      从JavaSE5起,提供了执行器(Executor)来简化并发编程。使用Executor,我们不需要收到创建Thread实例,将任务交给Excutor,至于把任务附着到线程上执行,又或者管理线程的生命周期,都不需要我们处理。

    public class CacheThreadPoolTest {
    
        public static void main(String[] args) {
        //通过静态方法创建newCacheThreadPool的Exector,可以创建FixedThreadPool、SingleThreadExecutor等类型的Executor ExecutorService exec
    = Executors.newCachedThreadPool(); for(int i=0;i<5;i++) {
           //将任务加到Executor上 exec.execute(
    new LiftOff()); }
         //开始顺序关闭Executor开启的线程,之后不能再添加任务到Executor,任务执行完后相应的线程将被关闭 exec.shutdown(); } }

      Executor很好地分离的任务及驱动任务的线程角色,客户端只需要专注于任务定义,而线程创建管理则交给Executor来负责。下面看看几种不同的Executor的特点:

    CachedThreadPool:Executor首选的线程池,通常会创建与所需数量相同的线程,在回收旧线程时停止创建新线程;
    FixedThreadPool:一次性预先创建固定数量的线程,可以节省后期线程创建的开销,并且防止滥用线程;
    SingleThreadExecutor:类似线程数量为1的FixedThreadPool,适用于所以任务都要在同一个线程中执行的情景(执行次序为任务被提交的先后顺序),还可以用于执行短任务;
    public class LiftOff implements Runnable{
    
        private int count=4;
    
        private static int taskId = 0;
    
        private int id = taskId++;
    
        private String getStatus() {
            return "task"+id+"("+count+")";
        }
    
        public void run() {
            try {
                while (count-->0) {
                    System.out.println(getStatus());
                    Thread.sleep(2000);
                }
            }catch (Exception e) {
                e.printStackTrace(System.out);
            }
        }
    
    }
    public class SingleThreadExecutorTest {
    
        public static void main(String[] args) {
            ExecutorService executor = Executors.newSingleThreadExecutor();
            for(int i=0;i<3;i++) {
                executor.execute(new LiftOff());
            }
        //这里一定要关闭线程,否则主线程将一直阻塞
         executor.shutdown(); } }
    输出:

    task0(3)
    task0(2)
    task0(1)
    task0(0)
    task1(3)
    task1(2)
    task1(1)
    task1(0)
    task2(3)
    task2(2)
    task2(1)
    task2(0)

    从任务中产生返回值

      无论是实现Runnable接口还是继承Thread类,都只是单独执行任务,没能返回值。要想从任务中产生返回值,需要使用Callable接口,其中重写call方法并得到参数化的Future类型的返回值。实现Callable接口的任务需要Executor使用submit来提交。

    //定义任务,实现Callable,返回值类型为String
    public
    class TaskWithResult implements Callable<String>{ private static int taskId = 0; private int id = taskId++; public String toString() { return "#task"+id; } public String call() throws Exception { return this.toString(); } }
    public class CallableTest {
    
        public static void main(String[] args) throws Exception{
            ExecutorService executor = Executors.newSingleThreadExecutor();
            List<Future<String>> futureList = new ArrayList<Future<String>>();
            for(int i=0;i<3;i++) {
           //通过executor的submit来提交,submit返回参数化的Future类型 futureList.add(executor.submit(
    new TaskWithResult())); } executor.shutdown(); for(Future<String> future: futureList) {
           //Future的get可以得到任务的真正返回值 System.out.println(future.get()); } } }
    输出:

    #task0
    #task1
    #task2

      需要注意的是,从Future中得到返回值时使用了get,但是get会引起阻塞直到任务完成并返回,为了避免阻塞,可以用isDone方法来查询Future是否完成。

    不闻不若闻之,闻之不若见之,见之不若知之,知之不若行之
  • 相关阅读:
    tcpprep 对IPV6的支持
    the server quit without updating pid file (/var/lib/mysql/localhost.localdomain.pid)
    servlet service() for servlet jsp throws null pointer exception
    tomcat开机启动
    mysql 允许远程访问
    spring的helloworld
    java中的那些坑
    关于struts2中的相对路径与绝对路径
    Powercenter Source Filter
    oracle删除当前用户的表
  • 原文地址:https://www.cnblogs.com/lauyu/p/5087579.html
Copyright © 2011-2022 走看看