JAVA之创建线程与线程池

匿名 (未验证) 提交于 2019-12-02 21:53:52

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创建的线程池具有此方法。
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!