JAVA之创建线程与线程池(转)

转自:https://www.e-learn.cn/content/java/653552

一、线程创建

JAVA创建线程的方式有三种,分别是:

  1. 继承Thread
  2. 实现Runnable
  3. 实现Callable

1、继承Thread 
通过继承抽象类Thread,创建MyThreadExtends对象,调用其start方法。

package Thread;

import java.util.concurrent.*;

public class TestThread {
    public static void main(String[] args) throws Exception {
        testExtends();
    }

    public static void testExtends() throws Exception {
        Thread t1 = new MyThreadExtends();
        Thread t2 = new MyThreadExtends();
        t1.start();
        t2.start();
    }
}

class MyThreadExtends extends Thread {
    @Override
    public void run() {
        System.out.println("通过继承Thread,线程号:" + currentThread().getName());
    }
}

2、实现Runnable 
通过实现接口Runnable,创建Runnable对象r,然后将r作为参数创建Thread对象t,最后调用t的start方法。

package Thread;

import java.util.concurrent.*;

public class TestThread {
    public static void main(String[] args) throws Exception {
         testImplents();
    }

    public static void testImplents() throws Exception {
        MyThreadImplements myThreadImplements = new MyThreadImplements();
        Thread t1 = new Thread(myThreadImplements);
        Thread t2 = new Thread(myThreadImplements, "my thread -2");
        t1.start();
        t2.start();
    }
}

class MyThreadImplements implements Runnable {
    @Override
    public void run() {
        System.out.println("通过实现Runable,线程号:" + Thread.currentThread().getName());
    }
}

3、实现Callable 
通过实现接口Callable ,创建Callable 对象c,然后以c为参数创建FutureTask 对象f,再以f为参数创建Thread对象t,调用t的start方法。此方法能通过FutureTask 对象f的get方法接收返回值。

package Thread;

import java.util.concurrent.*;

public class TestThread {
    public static void main(String[] args) throws Exception {
        testCallable();
    }

    public static void testCallable() throws Exception {
        Callable callable = new MyThreadCallable();
        FutureTask task = new FutureTask(callable);
        new Thread(task).start();
        System.out.println(task.get());
        Thread.sleep(10);//等待线程执行结束
        //task.get() 获取call()的返回值。若调用时call()方法未返回,则阻塞线程等待返回值
        //get的传入参数为等待时间,超时抛出超时异常;传入参数为空时,则不设超时,一直等待
        System.out.println(task.get(100L, TimeUnit.MILLISECONDS));
    }
}

class MyThreadCallable implements Callable {

    @Override
    public Object call() throws Exception {
        System.out.println("通过实现Callable,线程号:" + Thread.currentThread().getName());
        return 10;
    }
}

4、三者对比

  1. 继承Thread使用继承方式,由于JAVA中使用单继承方式,故对编码局限性较高;其余两种方式其实最后都是创建了Thread对象。
  2. Runable使用实现接口方式,属于常用方式。
  3. Callable能接收返回值,不过实现相比Runable较为繁琐,再不关注返回值的情况下不使用。

二、线程池

在实际项目使用中,多线程都与线程池同时出现,故在此说明线程池的创建(有很多博文认为线程池是实现多线程的一种方式,我并不认可。我认为线程池只是创建线程的一种方式,并不是实现)。 
在这里介绍的是使用java.util.concurrent包下的Executors创建线程池。 
1、使用代码示例


  public static void testExecutor() throws Exception {
        //创建单个线程的线程池(核心线程数与最大线程数都为1)
        ExecutorService executor1 = Executors.newSingleThreadExecutor();
        //创建定长线程池(核心线程数与最大线程数一样)
        ExecutorService executor2 = Executors.newFixedThreadPool(4);
        //创建定长线程池(定核心线程数,最大线程数为int的最大值)
        ExecutorService executor3 = Executors.newScheduledThreadPool(4);
        //创建无限长线程池(核心线程数为0,最大线程数为int最大值)
        ExecutorService executor4 = Executors.newCachedThreadPool();
       //创建线程池(所有参数自定义)
        ExecutorService executor5 = new ThreadPoolExecutor(4, 20, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
        MyThreadExecutor myThreadImplements = new MyThreadExecutor();
        MyThreadCallable myThreadCallable = new MyThreadCallable();
        for (int i = 0; i < 4; i++) {
            executor2.execute(myThreadImplements);
            Future futureTask = executor1.submit(myThreadCallable);
        }
    }

2、创建线程池方法详解


根据上述得知,Executors提供了五种实现方式,其作用如上,我们接下来看其源码。

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
    }
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
    //ScheduledThreadPoolExecutor继承ThreadPoolExecutor
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,new DelayedWorkQueue());
    }
     public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,
                              TimeUnit unit,BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
     public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,
                              TimeUnit unit,BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||maximumPoolSize <= 0 ||maximumPoolSize < corePoolSize ||keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?null :AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

通过关注以上源码,最终发现最后都是调用的ThreadPoolExecutor的 public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize, long keepAliveTime, TimeUnit unit,BlockingQueue workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)构造器,故重点关注此构造器。此构造器有7个参数,每个参数含义如下:

  1. corePoolSize 线程池核心线程数量。当新任务在方法 execute(java.lang.Runnable) 中提交时,如果运行的线程少于 corePoolSize,则创建新线程来处理请求,即使其他辅助线程是空闲的。
  2. maximumPoolSize 线程池最大线程数量。如果运行的线程多于 corePoolSize 而少于 maximumPoolSize,则仅当队列满时才创建新线程。
  3. keepAliveTime 当线程数大于corePoolSize 时,则这些多出的线程在空闲时间超过 keepAliveTime 时将会终止。
  4. unit 参数keepAliveTime 的时间单位。
  5. workQueue 执行前用于保持任务的队列。此队列仅保持由 execute 方法提交的 Runnable 任务。 
    A.队列保存策略:若运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队;若运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程;若无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。 
    B.队列选取通常策略: 
    a.直接提交:直接提交队列(如SynchronousQueue),此种队列将任务直接提交给线程而不保存他们,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。如newCachedThreadPool 
    b.无界队列。使用无界队列(如 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize(因此,maximumPoolSize 的值也就无效了)。当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。如newFixedThreadPool和newSingleThreadExecutor 
    c.有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。
  6. threadFactory 执行程序创建新线程时使用的工厂。若参数为空,则在同一个 ThreadGroup 中使用 Executors.defaultThreadFactory() 创建线程,并且这些线程具有相同的 NORM_PRIORITY 优先级和非守护进程状态。
  7. handler 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。一般不会自定义,而使用默认。

3、创建线程池方法对比


通过上述描述,对创建线程池方法进行对比分析:

  1. 若自身对性能有很大需求,且对于机器性能、代码能力等有足够自信,使用ThreadPoolExecutor的构造方法是最合适的。
  2. newSingleThreadExecutor()是构造只有一个线程的线程池,保存任务的队列是无界的,可接收所有任务,但是同时只有一个线程执行任务
  3. newFixedThreadPool()是构造可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程,在需要时使用提供的 ThreadFactory 创建新线程。
  4. newScheduledThreadPool()创建一个可重用线程池(最大线程数为int最大值),它可安排在给定延迟后运行命令或者定期地执行(因为使用DelayedWorkQueue()队列)。
  5. newCachedThreadPool()是构造一个可根据需要创建新线程的线程池(最大线程数为int最大值),但是在以前构造的线程可用时将重用它们。调用 execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。

4、线程池提交线程方法


executor2.execute(myThreadImplements);
Future futureTask = executor1.submit(myThreadCallable);
ScheduledFuture scheduledFuture = executor3.schedule(myThreadImplements, 100L, TimeUnit.SECONDS);

如上,提交方式有execute,submit,schedule三种(不是所有线程都可用此三种):

  1. execute属于ExecutorService的父接口Executor的方法,所有线程池都具有此方法。
  2. submit属于ExecutorService的方法。具有返回值Future ,当内部线程使用实现Callable方式是现实时(具有返回值),可以接收返回值。上述所有线程池都具有此方法。
  3. schedule属于ExecutorService的子孙类ScheduledThreadPoolExecutor方法。具有返回值Future ,当内部线程使用实现Callable方式是现实时(具有返回值),可以接收返回值。能实现定时任务和延迟任务。上述线程池中只有newScheduledThreadPool创建的线程池具有此方法。
发布了21 篇原创文章 · 获赞 2 · 访问量 4794

猜你喜欢

转载自blog.csdn.net/qq_21479345/article/details/90896389