创建线程方法?什么是线程池?线程池原理?线程池的线程复用?(由浅入深,全面剖析)

 四种创建多线程常用的方法:

  • 继承Thread

  • 实现Runnable接口

  • 使用Callable和FutureTask

  • 线程池

 

1. 继承Thread类创建多线程


创建一个多线程需要执行两个步骤,继承Thread类,创建一个新的线程类,比如命名为mythread类,重写run()方法,将需要并发执行的业务代码编写在run()方法中,那么启动线程的话,我们创建mythread类, 调用mythread.start()来启动线程就可以了。



2. 实现Runnable接口创建多线程


将需要异步执行的业务逻辑代码写在Runnable实现类的run()方法中,再将Runnable实例作为target执行目标传入Thread实例,其完整步骤如下:定义一个新类实现Runnable接口,实现Runnable接口中的run()抽象方法,将线程代码逻辑写在该run()实现方法中,通过Thread类创建线程对象,将Runnable实例作为实际参数传递给Thread类的构造器,由Thread构造器将该Runnable实例赋值给自己的target执行目标属性,调用Thread实例的start()方法启动线程,线程启动之后,线程的run()方法将被JVM执行,该run()方法将调用target属性的run()方法,从而完成Runnable实现类中业务代码逻辑的并发执行 。

// 1、创建一个类实现Runnable接口
class Number implements Runnable{
    // 2. 重写Runnable接口的run()
    @Override
    public void run() {
        for (int i = 0; i <100 ; i++) {
            if(i%2==0){
                // System.out.println(i);
                //输出线程名
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
}
public class ThreadRunnable {
    public static void main(String[] args) {
        // 3、创建实现Runnable接口类的对象
        Number number=new Number();
        // 4、将对象作为Thread类的参数
        Thread thread=new Thread(number,"线程1");
        thread.start();
    }
}

继承Thread和实现Runnable区别


继承Thread类用于多个线程并发完成各自的任务,访问各自的数据资源
实现Runnable接口用于多个线程并发完成同一个任务,访问同一份数据资源,数据共享资源需要使用原子类型或者进行线程同步控制


3. 使用Callable和FutureTask创建多线程

  • 创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。

  • 使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值

  • 使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)

  • 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

       

class cal implements Callable{
    private int num=0;
    @Override
    public Integer call() throws Exception {
        for (int i = 0; i <100 ; i++) {
            if(i%2==0){
                // System.out.println(i);
                //输出线程名
                System.out.println(Thread.currentThread().getName()+":"+i);
                num+=i;
            }
        }
        return num;
    }
}
 
public class CallableThread {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 将接口Callable实现类的对象作为FutureTask的参数
        FutureTask futureTask=new FutureTask(new cal());
        // 将FutureTask对象作为Thread的参数
        new Thread(futureTask, "窗口1").start();
        // 还可以获取Callable中call方法的返回值
        System.out.println("num的值:"+futureTask.get());
    }
}

 

Callable与Runnable区别

 

  • Runnable的唯一抽象方法run()没有返回值,也没受检查异常的异常声明,Callable接口的call()有返回值,并且声明了受检查异常,功能更强
  • 使用Runnable创建多线程,实现Runnable接口的实例作为Thread线程实例的target来使用
  • 使用Callable创建多线程,使用RunnableFuture作为Thread线程实例的target实例和获取异步执行的结果,其实现类是FutureTask

  


4. 通过线程池创建多线程

首先我们了解一下线程池:

什么是线程池?

        线程池就是管理一系列线程的资源池。当有任务要处理时,直接从线程池中获取线程来处理,处理完之后线程并不会立即被销毁,而是等待下一个任务。

使用线程池的好处:

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控

       

线程池的创建的两种方法

  • 通过TreadPoolExecutor构造
      ThreadPoolExecutor executor = new ThreadPoolExecutor(
                CORE_POOL_SIZE,
                MAX_POOL_SIZE,
                KEEP_ALIVE_TIME,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(QUEUE_CAPACITY),
                new ThreadPoolExecutor.CallerRunsPolicy());

        for (int i = 0; i < 10; i++) {
            //创建WorkerThread对象(WorkerThread类实现了Runnable 接口)
            Runnable worker = new MyRunnable("" + i);
            //执行Runnable
            executor.execute(worker);
        }
        //终止线程池
        executor.shutdown();
  • 使用Executors类创建

主要是四类线程池

  • FixedThreadPool

  •  SingleThreadExecutor 

  • CachedThreadPool 

  • ScheduledThreadPool 

 // 1、提供指定的线程数量的线程池
        ExecutorService executorService= Executors.newFixedThreadPool(10);
 
//        ThreadPoolExecutor service=(ThreadPoolExecutor) executorService;
//        // 设置线程池的属性
//        service.setCorePoolSize(15);
 
        executorService.execute(new MyThread1()); // 执行Runnable接口的实现类
        executorService.execute(new MyThread2()); // 执行Runnable接口的实现类
        Future submit = executorService.submit(new MyThread3());// 执行Callable接口的实现列

 

execute()与submit()区别

  • submit()可以接收两种入参:无返回值的Runnable类型的target执行目标实例和有返回值的Callable类型的target执行目标实例,execute()只接收无返回值的target执行目标实例或者无返回值的Thread实例

  • submit()有返回值,execute()没有返回值

  • submit()方法在提交异步target执行目标之后会返回Future异步任务实例,以便对target的异步执行过程进行控制,比如取消执行、获取结果等。execute()没有任何返回,target执行目标实例在执行之后没有办法对其异步执行过程进行控制,只能任其执行,直到其执行结束

         线程池必须手动通过 ThreadPoolExecutor 的构造函数来声明,避免使用Executors 类创建线程池,会有 OOM 风险。

Executors 返回线程池对象的弊端如下:

  • FixedThreadPoolSingleThreadExecutor:使用的是无界的LinkedBlockingQueue,任务队列最大长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致 OOM。

  • CachedThreadPool:使用的是同步队列 SynchronousQueue, 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致 OOM。

  • ScheduledThreadPoolSingleThreadScheduledExecutor : 使用的无界的延迟阻塞队列DelayedWorkQueue,任务队列最大长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致 OOM。

说白了就是:使用有界队列,控制线程创建数量。

除了避免 OOM 的原因之外,不推荐使用 Executors提供的两种快捷的线程池的原因还有:

  • 实际使用中需要根据自己机器的性能、业务场景来手动配置线程池的参数比如核心线程数、使用的任务队列、饱和策略等等。
  • 我们应该显示地给我们的线程池命名,这样有助于我们定位问题。

 

线程池的主要参数

  • corePoolSize:核心线程数。
  • maximumPoolSize:最大线程数。
  • keepAliveTime:空闲线程存活时间。
  • TimeUnit:时间单位。
  • BlockingQueue:线程池任务队列。
  • ThreadFactory:创建线程的工厂。
  • RejectedExecutionHandler:拒绝策略。

阻塞队列:线程池存放任务的队列,用来存储线程池的所有待执行任务。 

  • ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
  • LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
  • SynchronousQueue:一个不存储元素的阻塞队列,即直接提交给线程不保持它们。
  • PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
  • DelayQueue:一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素。
  • LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。与SynchronousQueue类似,还含有非阻塞方法。
  • LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。

拒绝策略:当线程池的任务超出线程池队列可以存储的最大值之后,执行的策略。


默认的拒绝策略有以下 4 种:

  • AbortPolicy:拒绝并抛出异常。

  • CallerRunsPolicy:使用当前调用的线程来执行此任务。
  • DiscardOldestPolicy:抛弃队列头部(最旧)的一个任务,并执行当前任务。
  • DiscardPolicy:忽略并抛弃当前任务。

线程池的工作原理

  • 如果当前运行的线程数小于核心线程数,那么就会新建一个线程来执行任务。
  • 如果当前运行的线程数等于或大于核心线程数,但是小于最大线程数,那么就把该任务放入到任务队列里等待执行。
  • 如果向任务队列投放任务失败(任务队列已经满了),但是当前运行的线程数是小于最大线程数的,就新建一个线程来执行任务。
  • 如果当前运行的线程数已经等同于最大线程数了,新建线程将会使当前运行的线程超出最大线程数,那么当前任务会被拒绝,饱和策略会调用RejectedExecutionHandler.rejectedExecution()方法。

 创建线程方法?什么是线程池?线程池原理?线程池的线程复用?(由浅入深,全面剖析)_第1张图片  

 

线程复用

       了解完线程池后,我们对比前面几种方法,他们创建的Thread实例在执行完成之后是不可复用的,实际工作中需要对已创建好的线程实例进行复用,需要用到线程池。

      线程的复用是通过while循环实现的,worker会首先获取当前的firstTask进行run,然后不停的循环从等待队列中获取新的任务task,如果有新任务则直接调用task的run方法,不会再去新建一个线程,从而实现复用。源码如下:

 final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // 释放锁 设置work的state=0 允许中断
        boolean completedAbruptly = true;
        try {
            //一直执行 如果task不为空 或者 从队列中获取的task不为空
            while (task != null || (task = getTask()) != null) {
                    task.run();//执行task中的run方法
                }
            }
            completedAbruptly = false;
        } finally {
            //1.将 worker 从数组 workers 里删除掉
            //2.根据布尔值 allowCoreThreadTimeOut 来决定是否补充新的 Worker 进数组 workers
            processWorkerExit(w, completedAbruptly);
        }
    }
 
 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(java,开发语言)