欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

多线程和线程池

程序员文章站 2022-07-09 21:28:07
...

多线程的两种最原始的创建方式

一、继承Thread类

extend Thread

二、实现Runnable接口

implement Runnable

然后重写run方法即可,实际上Thread也是实现了Runnable接口的。Thread可以大致看做成Runnable的子类,同时拥有start方法

多线程和线程池

三、启动线程

启动线需要用到Thread类的start方法

//继承Thread的方式
class ThreadDemo extends Thread{
@Override
    public void run() {
        for(int i=0;i<10;i++){
                System.out.println(Thread.currentThread().getName()+"------"+i);
        }
    }
}
  public static void main(String[] args) {
  //启动两个线程
  new ThreadDemo().start;
  new ThreadDemo().start;
  }
  //实现Runnable方式
  class RunnableDemo implements Runnable{
   @Override
    public void run() {
        for(int i=0;i<10;i++){
        System.out.println(Thread.currentThread().getName()+"------"+i);
         public static void main(String[] args) {
             RunnableDemo runnableDemo = new RunnableDemo();
             //启动两个线程
             new Thread(runnableDemo ).start;
             new Thread(runnableDemo ).start;
  }
        }
  }

线程池

线程池其实就是帮我们封装好的一些多线程类,如果需要用直接拿过来用。

比较常用的-线程池分类

  1. newCachedThreadPool
  2. newFixedThreadPool
  3. newSingleThreadExecutor
  4. newScheduledThreadPool
    以上这些都可以通过Executors对象创建出来,并通过Executor接口里唯一的execute方法创建线程。
  ExecutorService executorService = Executors.newCachedThreadPool();

        for (int i = 0;i<5;i++){
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    for(int j=0;j<20;j++){   try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                        System.out.println(Thread.currentThread().getName()+"-----"+j);
                    }
                }
            });
        }
        executorService.shutdown();

《阿里巴巴Java开发手册》中强制线程池不允许使用 Executors 去创建,而是通过 new ThreadPoolExecutor 实例的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

注意:

FixedThreadPool 和 SingleThreadExecutor : 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM。

CachedThreadPool 和 ScheduledThreadPool : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。

ThreadExecutor和Executors的关系

我们来看一下Executors的源码

/**
 * 创建固定数量线程的线程池
 *
 * 这是最后一个参数--阻塞队列调用的方法,长度是Integer.MAX_VALUE
 * public LinkedBlockingQueue() {
 *    this(Integer.MAX_VALUE);
 * }
 *
 * @param nThreads the number of threads in the pool
 * @return the newly created thread pool
 * @throws IllegalArgumentException if {@code nThreads <= 0}
 */
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}


/**
 * 创建只有一个线程的线程池
 *
 * 这是最后一个参数--阻塞队列调用的方法,长度也是Integer.MAX_VALUE
 * public LinkedBlockingQueue() {
 *    this(Integer.MAX_VALUE);
 * }
 *
 * @return the newly created single-threaded Executor
 */
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}


/**
 *创建一个缓冲线程池 
 *
 * 这是最后一个参数--阻塞队列调用的方法
 * public SynchronousQueue() {
 *    this(false);
 * }
 *
 * 它的第二个参数,maximumPoolSize 为Integer.MAX_VALUE
 *
 * @return the newly created thread pool
 */
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}


/**
 * Creates a thread pool that can schedule commands to run after a
 * given delay, or to execute periodically.
 *
 * 创建一个可以在给定延迟后再执行或定期执行命令的线程池
 *
 * ScheduledThreadPoolExecutor 是 ThreadPoolExecutor 的子类,代码如下:
 *
 public class ScheduledThreadPoolExecutor extends ThreadPoolExecutor
    implements ScheduledExecutorService {
        
        //这是下面调用的构造方法,其实是调用了父类的构造方法,这些参数都是下面分析的参数
        public ScheduledThreadPoolExecutor(int corePoolSize) {
           super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
           new DelayedWorkQueue());
        }
        
 }

 *
 * @param corePoolSize the number of threads to keep in the pool,
 * even if they are idle
 * @return a newly created scheduled thread pool
 * @throws IllegalArgumentException if {@code corePoolSize < 0}
 */
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {

    return new ScheduledThreadPoolExecutor(corePoolSize);
}

参数介绍:

corePoolSize

在线程池中保持的线程的数量,即使是这些线程没有被使用,除非设置了线程超时时间

maximumPoolSize

最大线程数量,当workQueue队列已满,放不下新的任务,再通过execute添加新的任务则线程池会再创建新的线程,线程数量大于corePoolSize但不会超过maximumPoolSize,如果超过maximumPoolSize,那么会抛出异常,如RejectedExecutionException。

keepAliveTime和unit

当线程池中线程数量大于workQueue,如果一个线程的空闲时间大于keepAliveTime,则该线程会被销毁。unit则是keepAliveTime的时间单位。

workQueue

阻塞队列,当线程池正在运行的线程数量已经达到corePoolSize,那么再通过execute添加新的任务则会被加到workQueue队列中,在队列中排队等待执行,而不会立即执行。

为什么说
FixedThreadPool 和 SingleThreadExecutor : 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致OOM。
CachedThreadPool 和 ScheduledThreadPool : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致OOM。
再结合注释看Executors类中的传参就明白了!
Executors中创建线程池:

FixedThreadPool 和 SingleThreadExecutor 传入的最后一个参数阻塞队列 ”workQueue“,默认的长度是INTEGER.MAX_VALUE,而它们允许的最大线程数量又是有限的,所以当请求线程的任务过多线程不够用时,它们会在队列中等待,又因为队列的长度特别长,所以可能会堆积大量的请求,导致OOM。

CachedThreadPool 和 ScheduledThreadPool 它们的阻塞队列长度有限,但是传入的第二个参数maximumPoolSize 为Integer.MAX_VALUE,这就意味着当请求线程的任务过多线程不够而且队列也满了的时候,线程池就会创建新的线程,因为它允许的最大线程数量是相当大的,所以可能会创建大量线程,导致OOM。

使用Executors类创建线程池与使用ThreadPoolExecutor类的区别就是使用ThreadPoolExecutor类可以自定义传入我们设置的线程池的参数,更加灵活。