Java多线程开发系列-线程管理

主题:

  • 线程的未捕获异常
  • 线程工厂
  • 线程暂停
  • 线程池

线程的未捕获异常

在线程异常的时候,多线程运行不能按照顺序执行过程中捕获异常的方式来处理异常,异常会被直接抛出到控制台(由于线程的本质,使得你不能捕获从线程中逃逸的异常。一旦异常逃逸出任务的run方法,它就会向外传播到控制台,除非你采用特殊的形式捕获这种异常。)

如下例:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadException implements Runnable{
  @Override
  public void run() {
    throw new RuntimeException();
  }
  //现象:控制台打印出异常信息,并运行一段时间后才停止
  public static void main(String[] args){
    //就算把线程的执行语句放到try-catch块中也无济于事
    try{
      ExecutorService exec = Executors.newCachedThreadPool();
      exec.execute(new ThreadException());
    }catch(RuntimeException e){
      System.out.println("Exception has been handled!");
    }
  }
}

使用UncaughtExceptionHandler可以捕获异常,并且重启新的线程来完成redo工作,这个例子里是重新初始化:init();

import io.github.viscent.mtia.util.Debug;
import io.github.viscent.mtia.util.Tools;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;

public class ThreadMonitorDemo {
  volatile boolean inited = false;
  static int threadIndex = 0;
  final static Logger LOGGER = Logger.getAnonymousLogger();
  final BlockingQueue<String> channel = new ArrayBlockingQueue<String>(100);

  public static void main(String[] args) throws InterruptedException {
    ThreadMonitorDemo demo = new ThreadMonitorDemo();
    demo.init();
    for (int i = 0; i < 100; i++) {
      demo.service("test-" + i);
    }

    Thread.sleep(2000);
    System.exit(0);
  }

  public synchronized void init() {
    if (inited) {
      return;
    }
    Debug.info("init...");
    WokrerThread t = new WokrerThread();
    t.setName("Worker0-" + threadIndex++);
    // 为线程t关联一个UncaughtExceptionHandler
    t.setUncaughtExceptionHandler(new ThreadMonitor());
    t.start();
    inited = true;
  }

  public void service(String message) throws InterruptedException {
    channel.put(message);
  }

  private class ThreadMonitor implements Thread.UncaughtExceptionHandler {
    @Override
    public void uncaughtException(Thread t, Throwable e) {
      Debug.info("Current thread is `t`:%s, it is still alive:%s",
          Thread.currentThread() == t, t.isAlive());

      // 将线程异常终止的相关信息记录到日志中
      String threadInfo = t.getName();
      LOGGER.log(Level.SEVERE, threadInfo + " terminated:", e);

      // 创建并启动替代线程
      LOGGER.info("About to restart " + threadInfo);
      // 重置线程启动标记
      inited = false;
      init();
    }

  }// 类ThreadMonitor定义结束

  private class WokrerThread extends Thread {
    @Override
    public void run() {
      Debug.info("Do something important...");
      String msg;
      try {
        for (;;) {
          msg = channel.take();
          process(msg);
        }
      } catch (InterruptedException e) {
        // 什么也不做
      }
    }

    private void process(String message) {
      Debug.info(message);
      // 模拟随机性异常
      int i = (int) (Math.random() * 100);
      if (i < 2) {
        throw new RuntimeException("test");
      }
      Tools.randomPause(100);
    }
  }// 类ThreadMonitorDemo定义结束
}

 

线程工厂

工厂设计模式是java中最常用的设计模式之一。它是一种创造性的模式,可用于开发一个或多个类需要的对象。有了这个工厂,我们可以集中创建对象。
创造逻辑的集中给我们带来了一些优势,例如:
很容易更改所创建对象的类或创建这些对象的方式。
很容易为有限的资源限制对象的创建。例如,我们只能有N个类型的对象。
很容易生成关于对象创建的统计数据。

线程工厂实际上是一个接口:

public interface ThreadFactory {
  Thread newThread(Runnable r);
}

实际上就是使用工厂模式在创建线程的时候统一操作。
ThreadPoolExecutor具有ThreadFactory作为参数的构造函数。执行器创建新线程时使用此工厂。
使用ThreadFactory,可以自定义由executor创建的线程,以便它们具有正确的线程名、优先级、设置为daemon等。

import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

public class CustomThreadFactory implements ThreadFactory
{
   private int          counter;
   private String       name;
   private List<String> stats;

   public CustomThreadFactory(String name)
   {
      counter = 1;
      this.name = name;
      stats = new ArrayList<String>();
   }

   @Override
   public Thread newThread(Runnable runnable)
   {
      Thread t = new Thread(runnable, name + "-Thread_" + counter);
      t.setDaemon(true);//守護線程
      t.setPriority(Thread.MAX_PRIORITY );//統一設置優先級
      counter++;
      stats.add(String.format("Created thread %d with name %s on %s \n", t.getId(), t.getName(), new Date()));
      return t;
   }

   public String getStats()
   {
      StringBuffer buffer = new StringBuffer();
      Iterator<String> it = stats.iterator();
      while (it.hasNext())
      {
         buffer.append(it.next());
      }
      return buffer.toString();
   }
   
   public static void main(String[] args)
   {
     CustomThreadFactory factory = new CustomThreadFactory("CustomThreadFactory");
     Task task = new Task();
     Thread thread;
     System.out.printf("Starting the Threads\n\n");
     for (int i = 1; i <= 10; i++)
     {
      thread = factory.newThread(task);
      thread.start();
     }
     System.out.printf("All Threads are created now\n\n");
     System.out.printf("Give me CustomThreadFactory stats:\n\n" + factory.getStats());
   }
}

class Task implements Runnable
{
   @Override
   public void run()
   {
      try
      {
         TimeUnit.SECONDS.sleep(2);
      } catch (InterruptedException e)
      {
         e.printStackTrace();
      }
   }
}

线程暂停

有3种暂停的方式

  • 1、自己进入睡眠暂停;
  • 2、使用CyclicBarrier,在某个阶段,多个线程统一暂停(await()),这里有介绍https://www.cnblogs.com/starcrm/p/12469364.html
  • 3、使用Condition信号暂停;

第一种情况的例子:

try {
  Thread.sleep(2000);
} catch (InterruptedException ex) {
}

第三种情况的例子,通过主线程进行控制:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class PauseControl extends ReentrantLock {
  private static final long serialVersionUID = 176912639934052187L;
  // 线程暂挂标志
  private volatile boolean suspended = false;
  private final Condition condSuspended = newCondition();

  /**
   * 暂停线程
   */
  public void requestPause() {
    suspended = true;
  }

  /**
   * 恢复线程
   */
  public void proceed() {
    lock();
    try {
      suspended = false;
      condSuspended.signalAll();
    } finally {
      unlock();
    }
  }

  /**
   * 当前线程仅在线程暂挂标记不为true的情况下才执行指定的目标动作。
   *
   * @targetAction 目标动作
   * @throws InterruptedException
   */
  public void pauseIfNeccessary(Runnable targetAction) throws InterruptedException {
    lock();
    try {
      while (suspended) {
        condSuspended.await();
      }
      targetAction.run();
    } finally {
      unlock();
    }
  }
}

import java.util.Scanner;

public class ThreadPauseDemo {
  final static PauseControl pc = new PauseControl();

  public static void main(String[] args) {
    final Runnable action = new Runnable() {
      @Override
      public void run() {
        Debug.info("Master,I'm working...");
        Tools.randomPause(300);
      }
    };
    Thread slave = new Thread() {
      @Override
      public void run() {
        try {
          for (;;) {
            pc.pauseIfNeccessary(action);
          }
        } catch (InterruptedException e) {
          // 什么也不做
        }
      }
    };
    slave.setDaemon(true);
    slave.start();
    askOnBehaveOfSlave();
  }

  static void askOnBehaveOfSlave() {
    String answer;
    int minPause = 2000;
    try (Scanner sc = new Scanner(System.in)) {
      for (;;) {
        Tools.randomPause(8000, minPause);
        pc.requestPause();
        Debug.info("Master,may I take a rest now?%n");
        Debug.info("%n(1) OK,you may take a rest%n"
            + "(2) No, Keep working!%nPress any other key to quit:%n");
        answer = sc.next();
        if ("1".equals(answer)) {
          pc.requestPause();
          Debug.info("Thank you,my master!");
          minPause = 8000;
        } else if ("2".equals(answer)) {
          Debug.info("Yes,my master!");
          pc.proceed();
          minPause = 2000;
        } else {
          break;
        }
      }// for结束
    }// try结束
    Debug.info("Game over!");
  }
}

线程池

线程池是预初始化线程的集合。一般来说,集合的大小是固定的,但不是强制的。它有助于使用相同的线程执行N个任务。如果有比线程更多的任务,那么任务需要在队列结构(FIFO–先进先出)中等待。
当任何线程完成其执行时,它都可以从队列中获取新任务并执行它。所有任务完成后,线程将保持活动状态,并等待线程池中的更多任务。
线程池的意义在于:
1、减少在创建和销毁线程上所花的时间以及系统资源的开销,提升任务执行性能。
2、控制进程中线程数量的峰值,避免系统开销过大。
通过线程池,可创建一定数量的线程,并由线程池管理。在需要执行任务时,直接使用其中的线程。任务执行完成后,线程保留,并可用于执行下一个任务。如果任务比线程多,则等待线程空闲。

直接构建ThreadPoolExecutor

我们可以使用以下构造函数实例化ThreadPoolExecutor,其中ThreadFactory和RejectedExecutionHandler是可选参数,默认值分别是Executors.defaultThreadFactory()和ThreadPoolExecutor.AbortPolicy

ThreadPoolExecutor(int corePoolSize,
                   int maximumPoolSize, 
                   long keepAliveTime, 
                   TimeUnit unit, 
                   BlockingQueue<Runnable> workQueue, 
                   ThreadFactory threadFactory, 
                   RejectedExecutionHandler handler) 
  • int corePoolSize : 定义核心池大小。对于每个新请求,即使池中有空闲线程,也会根据核心池大小创建一个新线程
  • int maximumPoolSize : 当发出请求并且核心池大小中的所有线程都忙时,将创建一个新线程,直到它达到最大池大小。在最大池大小之后,所有新请求都将进入队列。
  • long keepAliveTime: 这是等待空闲线程死亡的时间。只有当线程计数大于核心池大小且小于或等于最大核心池大小时,空闲线程才会在keepAliveTime之后死亡。
  • TimeUnit unit: keepAliveTime参数的时间单位
  • BlockingQueue<Runnable> workQueue : BlockingQueue是一个队列,如果一个线程想要获取一个元素,并且队列是空的,那么线程将被阻塞,并等待元素在队列中可用。以同样的方式添加元素时,如果队列中没有空间,线程将被阻塞并等待获得可用空间。
  • ThreadFactory threadFactory : 这是可选参数。传递用户定义的ThreadFactory
  • RejectedExecutionHandler handler: 这是可选参数。在两种情况下,ThreadPoolExecutor.execute()可以拒绝新任务。
    •   1、执行器已关闭。
    •   2、超过工作队列容量和最大线程使用有限的界限,即它们是饱和时
  • ThreadPoolExecutor.AbortPolicy:它中止任务并始终抛出RejectedExecutionException。
  • ThreadPoolExecutor.CallerRunsPolicy:它自己(客户端)执行被拒绝的任务。
  • ThreadPoolExecutor.DiscardPolicy:删除任务。
  • ThreadPoolExecutor.DiscardOldestPolicy:当ThreadPoolExecutor.execute()由于工作队列的有限边界和最大限制而拒绝任务时,此策略只将任务放在工作队列的头。

默认策略是ThreadPoolExecutor.AbortPolicy。

例子

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExecutorDemoOne {
    public static void main(final String[] args) throws Exception {
        final ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 3, 100, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>(), new ThreadPoolExecutor.CallerRunsPolicy());
        executor.execute(new BookReader("Ramayan"));
        executor.execute(new BookReader("Mahabharat"));
        executor.execute(new BookReader("Veda"));
        System.out.println("Old Max Pool Size:"+ executor.getMaximumPoolSize());
        executor.setMaximumPoolSize(4);
        System.out.println("New Max Pool Size:"+ executor.getMaximumPoolSize());        
        executor.shutdown();
    }
} 

class BookReader implements Runnable {
  private String bookName;
  public BookReader(String bookName) {
    this.bookName = bookName;
  }
  @Override
  public void run() {
    for(int i = 0; i<5; i++) {
      System.out.println("Reading book: "+ bookName);
      try {

        Thread.sleep(200);

    } catch (InterruptedException ex) {
        System.out.println("I'm interrupted");
    }
    }
  }
} 

 

使用Executors工厂方法创建ThreadPoolExecutor实例

可以使用Executors类提供的静态工厂方法来获取ThreadPoolExecutor,而不是直接使用上述构造函数之一创建ThreadPoolExecutor的实例。

  • newCachedThreadPool()–创建一个线程池,该线程池根据需要创建新线程,提供无容量的阻塞队列 SynchronousQueue,因此任务提交之后,将会创建新的线程执行;线程空闲超过60s将会销毁 。
  • newCachedThreadPool(ThreadFactory ThreadFactory)–创建一个线程池,该线程池根据需要创建新线程,但在可用时将重用先前构造的线程,并在需要时使用提供的ThreadFactory创建新线程。
  • newFixedThreadPool(int nThreads)–创建一个线程池,该线程池重用在共享的无边界队列上操作的固定数量的线程。构造一个固定线程数目的线程池,配置的corePoolSize与maximumPoolSize大小相同,同时使用了一个无界LinkedBlockingQueue存放阻塞任务,因此多余的任务将存在再阻塞队列,不会由RejectedExecutionHandler处理
  • newFixedThreadPool(int nThreads,ThreadFactory ThreadFactory)–创建一个线程池,该线程池重用在共享的无边界队列上操作的固定数量的线程,在需要时使用提供的ThreadFactory创建新线程。
  • newSingleThreadExecutor()–创建一个执行器,该执行器使用一个工作线程并在无限队列上操作,由一个线程串行执行任务。
  • newSingleThreadExecutor(ThreadFactory ThreadFactory)–创建一个执行器,该执行器使用一个在无限队列上操作的工作线程,并在需要时使用提供的ThreadFactory创建一个新线程。

 

Executors的例子

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorExp {
  
  public static void main(String[] args) {
      ExecutorService executor = Executors.newSingleThreadExecutor();
      for(int i = 0; i < 4; i++) {
          executor.execute(new VoidTask());    
      }
      executor.shutdown();
  }

}
class VoidTask implements Runnable{
  @Override
  public void run() {
      System.out.println("Executing task (thread name)- " + Thread.currentThread().getName());
      // delay to keep the thread busy
      // so that pool is used
      try {
          Thread.sleep(500);
      } catch (InterruptedException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
      }
  }
}

 

newFixedThreadPool.

此方法创建一个线程池,该线程池重用在共享的无界队列上操作的固定数量的线程。使用此方法时,内部Executors类使用以下参数创建ThreadPoolExecutor实例:

new ThreadPoolExecutor(nThreads, nThreads,
          0L, TimeUnit.MILLISECONDS,
          new LinkedBlockingQueue());

实例

public class ExecutorExp {
  
  public static void main(String[] args) {
      ExecutorService executor = Executors.newFixedThreadPool(2);
      for(int i = 0; i < 4; i++) {
          executor.execute(new VoidTask());    
      }
      executor.shutdown();
  }

}
class VoidTask implements Runnable{
  @Override
  public void run() {
      System.out.println("Executing task (thread name)- " + Thread.currentThread().getName());
      // delay to keep the thread busy
      // so that pool is used
      try {
          Thread.sleep(500);
      } catch (InterruptedException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
      }
  }
}

newCachedThreadPool

该线程池根据需要创建新线程,但在以前构造的线程可用时将重用它们。使用此方法时,内部Executors类使用以下参数创建ThreadPoolExecutor实例

new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                       60L, TimeUnit.SECONDS,
                       new SynchronousQueue<Runnable>());

实例

public class ExecutorExp {
  
  public static void main(String[] args) {
      ExecutorService executor = Executors.newCachedThreadPool();
      for(int i = 0; i < 40; i++) {
          executor.execute(new VoidTask());    
      }
      executor.shutdown();
  }

}
class VoidTask implements Runnable{
  @Override
  public void run() {
      System.out.println("Executing task (thread name)- " + Thread.currentThread().getName());
      // delay to keep the thread busy
      // so that pool is used
      try {
          Thread.sleep(500);
      } catch (InterruptedException e) {
          // TODO Auto-generated catch block
          e.printStackTrace();
      }
  }
}

//为了验证最大线程数Integer.MAX_VALUE,我们看一下结果,感觉这个
Executing task (thread name)- pool-1-thread-3
Executing task (thread name)- pool-1-thread-4
Executing task (thread name)- pool-1-thread-1
Executing task (thread name)- pool-1-thread-2
Executing task (thread name)- pool-1-thread-7
Executing task (thread name)- pool-1-thread-8
Executing task (thread name)- pool-1-thread-5
Executing task (thread name)- pool-1-thread-6
Executing task (thread name)- pool-1-thread-11
Executing task (thread name)- pool-1-thread-10
Executing task (thread name)- pool-1-thread-9
Executing task (thread name)- pool-1-thread-12
Executing task (thread name)- pool-1-thread-15
Executing task (thread name)- pool-1-thread-13
Executing task (thread name)- pool-1-thread-14
Executing task (thread name)- pool-1-thread-16
Executing task (thread name)- pool-1-thread-17
Executing task (thread name)- pool-1-thread-18
Executing task (thread name)- pool-1-thread-19
Executing task (thread name)- pool-1-thread-20
Executing task (thread name)- pool-1-thread-21
Executing task (thread name)- pool-1-thread-23
Executing task (thread name)- pool-1-thread-22
Executing task (thread name)- pool-1-thread-24
Executing task (thread name)- pool-1-thread-25
Executing task (thread name)- pool-1-thread-26
Executing task (thread name)- pool-1-thread-27
Executing task (thread name)- pool-1-thread-28
Executing task (thread name)- pool-1-thread-29
Executing task (thread name)- pool-1-thread-30
Executing task (thread name)- pool-1-thread-31
Executing task (thread name)- pool-1-thread-32
Executing task (thread name)- pool-1-thread-33
Executing task (thread name)- pool-1-thread-34
Executing task (thread name)- pool-1-thread-35
Executing task (thread name)- pool-1-thread-36
Executing task (thread name)- pool-1-thread-37
Executing task (thread name)- pool-1-thread-39
Executing task (thread name)- pool-1-thread-38
Executing task (thread name)- pool-1-thread-40
View Code

 

构造有定时功能的线程池

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

public static ScheduledExecutorService newScheduledThreadPool(
            int corePoolSize, ThreadFactory threadFactory) {
        return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
    }

public ScheduledThreadPoolExecutor(int corePoolSize,
                             ThreadFactory threadFactory) {
        super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS,
              new DelayedWorkQueue(), threadFactory);
    }

实例

import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
 
public class ScheduledThreadPoolExecutorExample 
{
    public static void main(String[] args) 
    {
        ScheduledThreadPoolExecutor executor = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(2);
         
        DelayTask task = new DelayTask("Repeat Task");
        System.out.println("Created : " + task.getName());
         
        executor.scheduleWithFixedDelay(task, 2, 2, TimeUnit.SECONDS);
    }
}
 
class DelayTask implements Runnable {
    private String name;
 
    public DelayTask(String name) {
        this.name = name;
    }
 
    public String getName() {
        return name;
    }
 
    public void run() {
        System.out.println("Executing : " + name + ", Current Seconds : " + new Date().getSeconds());
    }
}

 

Callable接口

Callable接口与Runnable接口相似,都用于定义线程的可执行任务。Callable在JDK 1.5引入,与Runnable相比,有三个优势:
1、可以在任务中抛出异常
2、可以终止任务
3、可以获取任务的返回值
这时候就会出现一个问题:新旧两种定义线程任务的方式,怎么前后兼容呢?
这时候就出现了FutureTask这个类

public class FutureTask<V> implements RunnableFuture<V> {
    // 封装实现Callable接口的任务
    public FutureTask(Callable<V> callable) { //...}
    // 封装实现Runnable接口的任务,result是返回值,在任务完成后赋值(futureTask.get()会返回这个值)。
    public FutureTask(Runnable runnable, V result) {//...}
}
public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

使用FutureTask包装Runnable

import java.util.concurrent.Callable;

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for(int i=0;i<3;i++){
            System.out.println("num="+(i+1));
        }
    }
}
import java.util.concurrent.FutureTask;

public class Test {
    public static void main(String[] agrs){
        String str = "default value";
        FutureTask<String> task = new FutureTask<String>(new MyRunnable(),str);
        new Thread(task).start();
        System.out.println(getFutureValue(task));
        System.out.println("after task.get()!");
    }

    public static String getFutureValue(FutureTask<String> task){
        String str = "default value1";
        try{
            str = task.get();
        }catch (Exception e){
            System.out.println("task is canceled!");
        }
        return str;
    }
}

线程池管理线程任务

通过ExecutorService,可以使用4个方法执行线程任务。其中3个执行Runnable任务:
其中3个执行Runnable任务:

  • void execute(Runnable command);
  • < T > Future< T > submit(Runnable task, T result);
  • Future< ? > submit(Runnable task);

1个执行Callable任务:

  • < T > Future< T > submit(Callable< T > task); //执行Callable任务

只有通过submit方法执行的线程任务,才能获取到Future,才能通过Future管理线程。
示例代码如下:

import java.util.concurrent.Callable;

public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception{
        int num=0;
        for(int i=0;i<3;i++){
            num++;
            Thread.sleep(100);
            System.out.println("num="+num);
        }
        return String.valueOf(num);
    }
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class Test {
    public static void main(String[] agrs){
        ExecutorService service = Executors.newFixedThreadPool(2);
        Future<String> future = service.submit(new MyCallable()); // 使用submit()执行Callable任务
        System.out.println("执行结果:"+getFutureValue(future));
        System.out.println("after task.get()!");
        service.shutdown();
    }

    public static String getFutureValue(Future<String> task){
        String str = "default value";
        try{
            str = task.get();
        }catch (Exception e){
            System.out.println("task is canceled!");
        }
        return str;
    }
}

 

Executor和ExecutorService的区别

见图

Executors 是工厂helper类,包含几个用于为您创建预配置线程池实例的方法。这些类是一个很好的线程池起点。如果不需要应用任何自定义微调,请使用它。
Executor和ExecutorService接口用于在Java中处理不同的线程池实现。通常,您应该保持代码与线程池的实际实现分离,并在整个应用程序中使用这些接口。
Executor接口有一个execute方法来提交Runnable实例以供执行。

posted @ 2020-03-18 15:28  昕友软件开发  阅读(925)  评论(0编辑  收藏  举报
欢迎访问我的开源项目:xyIM企业即时通讯