Java之多线程创建

版权声明:欢迎转载评论~哈哈哈哈请标明出处呀 https://blog.csdn.net/legendaryhaha/article/details/88627263


前言

进程是资源分配的最小单位,而线程是执行任务的最小的单位。进程里的线程可以共享该进程拥有的资源,而进程之间不可以,进程奔溃了,在保护模式下,不会影响其他进程,而线程一旦出问题了,该进程也要GG思密达。总而言之,创建一个线程比创建一个进程要省事得多儿,这也是当今为了充分利用cpu性能,处理大规模并发时常用的伎俩之一。


传统的线程创建

Java中创建线程的方式有四种,具体方式如下:

继承Thread类

在这里插入图片描述
在这里插入图片描述

实现Runnable接口

在这里插入图片描述
在这里插入图片描述


两者的共同点

无论如何创建线程,通过以上的运行,我们可以发现,线程的运行不是根据代码的顺序来执行的,而是随机的,不是说先开启就先运行,有时有些线程可能还设有优先级。像上面的运行结果,main主线程的优先级就明显比创建的线程的优先级高,1总是先被打印出来。

它们都需要通过start()方法进行开启,没有开启,就直接运行run()方法,跟我们平常调用其他方法一样,没有任何差别,谁先调用谁就先执行,没有随机的说法。这个点也是面试容易遇到的地方,给你个创建线程的程序,让你说出运行结果,此时要注意是否有先调用start方法。

两者都有一个公共的缺陷:在执行完任务之后如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,因为在源码中,Runnable接口申明的run方法就是void类型的,没有返回值。但在jdk1.5之后,引入的callable和future解决了这个问题。


两者差别

通过实现Runnable接口创建的线程要比继承Thread这种方式灵活得多。其实查看源码,我们可以知道,Thread类其实也是继承于Runnable接口。之所以这样处理,无非为了解决Java类的单继承问题,因为继承了Thread类,当有其他类也需要继承时,就很无奈了。另外,从设计模式的角度来说,这种依赖抽象的东西本身就要比依赖细节稳定的多。


附 jdk1.8 API Thread的构造方法:
在这里插入图片描述


JDK 1.5开始出现的线程创建

在jdk1.5时,开始增加了两种线程创建的办法即Callable和Future,它们弥补了上面所说的以往创建线程方式的公共缺陷。在执行完任务后,通过这两种方式,我们可以获得返回结果。

场景模拟:现在有一个项目,它划分成A模块和B模块,它们可以同时进行开发,最后组装起来就行,采用Thread和Runnable方式:

package com.fang.pratice;

public class WorkThread {
    public static void main(String[] args) {
        A threadA = new A();
        threadA.start();
        
        B threadB = new B();
        threadB.start();
        
        try {
            //让父线程等待子线程结束之后才能继续运行
            threadA.join();
            threadB.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        add(threadA,threadB);
    }
    private static void add(A a, B b){
        System.out.println("合并完成");
    }
    static class A extends Thread{
        @Override
        public void run() {
           System.out.println("正在完成A");
           try {
            Thread.sleep(3000);//完成A需要1000毫秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
           System.out.println("A完成");
        }
    }
    static class B extends Thread{
        @Override
        public void run() {
           System.out.println("正在完成B");
           try {
            Thread.sleep(2000);//完成A需要2000毫秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
           System.out.println("B完成");
        }
    }
}

在这里插入图片描述


通过Callable和FutureTask创建线程

package com.fang.pratice;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class WorkThreadByCallable {
    public static void main(String[] args) {
        Callable<Boolean> threadA = new Callable<Boolean>() {
            @Override
            public Boolean call() throws Exception {
                System.out.println("正在完成A");
                Thread.sleep(3000);//完成A需要3000毫秒
                System.out.println("A完成");
                return true;
            }
        };
        Callable<Boolean> threadB = new Callable<Boolean>() {
            @Override
            public Boolean call() throws Exception {
                System.out.println("正在完成B");
                Thread.sleep(1000);//完成A需要1000毫秒
                System.out.println("B完成");
                return true;
            }
        };
        //创建一个 FutureTask ,它将在运行时执行给定的 Callable
        FutureTask<Boolean> taskA = new FutureTask<Boolean>(threadA);
        FutureTask<Boolean> taskB = new FutureTask<Boolean>(threadB);
        
        new Thread(taskA).start();
        new Thread(taskB).start();
       /**
        * 判断任务A线程和任务B线程是否完成
        * 其实在主线程这里判断,感觉不太合适,因为主线程优先级较高,程序运行后会先执行
        * 但出于学习吧,能了解方法的含义和用途就ok吧
        */
      if(!taskA.isDone()||!taskB.isDone()){
            System.out.println("A或B还没好,此时可已调用cancel方法取消任务");
       }
        try {
            /**
             * 获得任务A和B的返回值,如果A和B还没完成,会等着
                                但不会一直等着,通过源码我们可以知道,当任务还未完成时
                               调用了awaitDone方法进行等待
                get方法也可以设置超时时间,第一参数为超时时间
                              第二个参数为时间单位,这里指定1s会抛出超时异常
            **/
            try {
                if(taskA.get(1,TimeUnit.SECONDS)&&taskB.get()){
                    add(threadA,threadB);
                }
            } catch (TimeoutException e) {
                taskA.cancel(true);//取消线程的任务
                System.out.println("任务超时");
              // e.printStackTrace();
            }
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ExecutionException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }        
    }
    private static void add(Callable<Boolean> threadA, Callable<Boolean> threadB) {
        // TODO Auto-generated method stub  
    }
}

callable其实就是一接口,跟Runnable差不多,只不多有返回值,它需要有一容器来包装它,就如上线的FutureTask那样,当然,你也可以和线程池一起搭配使用。


通过线程池创建线程

前面说的创建线程的方法,都是任务请求时,创建线程(新建),然后经过就绪、运行、阻塞这若干个状态(不是指都会进行,也可能只有运行),最后殊途同归,都会死亡。这显然给服务器带来了更多的压力,因为频繁的创建、销毁线程,不仅浪费资源还造成时间上的延迟。阿里巴巴Java开发规范手册就做了明确的规定:使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。

即通过线程池,我们可以统管理线程,如:设置程序创建线程的阈值、核心线程的数量(下面有解释),超时时间,从而可以实现线程的复用。

线程池的种类很多,这里以ThreadPoolExecutor为例子,在开始一个例子前,说一下它构造方法,几个参数的含义吧:

  • corePoolSize:该线程池中核心线程数最大值,就是线程池中至少应该有多少线程,这些线程会一直待着,不会随便就消失。
  • maximumPoolSize 该线程池中线程总数的最大值,核心线程不够用时,就需要非核心线程了,只要小于等于线程最大值,就可以创建。
  • keepAliveTime :该线程池中非核心线程闲置超时时长,非核心的线程在执行任务后,不会立即消失,可通过设置这个值来决定它存活的时间。
  • TimeUnit unit :时间单位,可指定时间的单位
  • BlockingQueue<Runnable> workQueue,阻塞的队列,线程都在工作时,新加入的队列在这里排队

在这里插入图片描述
除此之外,他还有三个构造函数:

  1. 第二构造函数增加了 ThreadFactory threadFactory 参数:线程工厂,你可以指定创建线程的方式,这是一个接口,new它的时候需要实现他接口中的Thread newThread(Runnable r)方法
    在这里插入图片描述
  2. 第三个构造函数在第一个构造函数的基础上增加的是RejectedExecutionHandler handler参数:当线程无法执行新任务时,决定怎样处理,如线程池中的线程数量已经达到最大值、线程池关闭导致的,默认情况下,当线程池无法处理新线程时,会抛出一个RejectedExecutionException,因为源码中有一个内部类,为默认这个东西
    在这里插入图片描述
  3. 第四个构造参数无非就增加了第二个和第三个构造函数所增加的属性。

代码中已经给出注释了,但还是有一点要说一下,ThreadPoolExecutor提供的execute(Runnable run)方法,参数是Runnable类型的,不过它继承了AbstractExecutorService,它提供的submit(Callabletask) 方法可以提交Callable类型的任务(相当于start),并且可以返回代表任务待处理结果的Future。要是觉得麻烦,也可以改成实现Runnable接口

package com.fang.pratice;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class TestExecutor {
    public static void main(String[] args) { 
        /**
         * 对照源码
         * corePoolSize,int,第一个参数,该线程池中核心线程数最大值
         *  maximumPoolSize ,int,第二个参数,该线程池中线程总数的最大值
         *  keepAliveTime ,long, 该线程池中非核心线程闲置超时时长
         *  TimeUnit unit  ,时间单位,可指定时间的单位
         *  BlockingQueue<Runnable> workQueue,阻塞的队列,线程都在工作时,
         *  新加入的队列在这里排队
         */
        ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 6, 200, TimeUnit.MILLISECONDS,
                new ArrayBlockingQueue<Runnable>(2));
         
        for(int i=0;i<5;i++){
            ThreadTest myTask = new ThreadTest(i);
            executor.submit(myTask);
            System.out.println("池中当前的线程数:"+executor.getPoolSize()+",队列中等待执行的任务数目:"+
            executor.getQueue().size()+",已执行玩别的任务数目:"+executor.getCompletedTaskCount());
        }
        executor.shutdown();
    }

}
class ThreadTest implements Callable<Integer>{
    @SuppressWarnings("unused")
    private int name;
    public ThreadTest() {}
    
    public ThreadTest(int i) {
        name = i;
    }

    @Override
    public Integer call() throws Exception {
        // TODO Auto-generated method stub
        return null;
    }
    
}

关系图

列出了一些常见的,并不代表所有:
在这里插入图片描述
线程池,可能有人直接通过一个线程池的大工厂Executors来创建具体的线程池工厂,但阿里巴巴Java开发规范手册就指出了这种方式的弊端。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/legendaryhaha/article/details/88627263