java编写属于自己的线程池

什么是线程池

线程池就是以一个或多个线程[循环执行]多个应用逻辑的线程集合.

一般而言,线程池有以下几个部分:

完成主要任务的一个或多个线程.
用于调度管理的管理线程.
要求执行的任务队列.

线程池的作用:

线程池作用就是限制系统中执行线程的数量。
根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。

自己实现线程池

根据如上对线程池的理解,我们自己编写一个属于自己的简单线程池:

简单的线程池接口:

public interface ThreadPool<Job extends Runnable>{
  //执行一个任务(Job),这个Job必须实现Runnable
  void execute(Job job);
 //关闭线程池
 void shutdown();
 //增加工作者线程,即用来执行任务的线程
 void addWorkers(int num);
 //减少工作者线程
 void removeWorker(int num);
 //获取正在等待执行的任务数量
 void getJobSize();
}

客户端可以通过execute(Job)方法将Job提交入线程池来执行,客户端完全不用等待Job的执行完成。除了execute(Job)方法以外,线程池接口提供了增加/减少工作者线程以及关闭线程池的方法。每个客户端提交的Job都会进入到一个工作队列中等待工作者线程的处理。

线程池接口的默认实现

public class DefaultThreadPool<Job extends Runnable> implements ThreadPool<Job>{

  // 线程池维护工作者线程的最大数量
  private static final int MAX_WORKER_NUMBERS = 10;
  // 线程池维护工作者线程的默认值
  private static final int DEFAULT_WORKER_NUMBERS = 5;
  // 线程池维护工作者线程的最小数量
  private static final int MIN_WORKER_NUMBERS = 1;
  // 维护一个工作列表,里面加入客户端发起的工作
  private final LinkedList<Job> jobs = new LinkedList<Job>();
  // 工作者线程的列表
  private final List<Worker> workers = Collections.synchronizedList(new ArrayList<Worker>());
  // 工作者线程的数量
  private int workerNum;
  // 每个工作者线程编号生成
  private AtomicLong threadNum = new AtomicLong();

 //生成线程池
public DefaultThreadPool() {
    this.workerNum = DEFAULT_WORKER_NUMBERS;
    initializeWorkers(this.workerNum);
  }

  public DefaultThreadPool(int num) {
    if (num > MAX_WORKER_NUMBERS) {
      this.workerNum =DEFAULT_WORKER_NUMBERS;
    } else {
      this.workerNum = num;
    }
    initializeWorkers(this.workerNum);
  }
//初始化每个工作者线程
private void initializeWorkers(int num) {
    for (int i = 0; i < num; i++) {
      Worker worker = new Worker();
      //添加到工作者线程的列表
      workers.add(worker);
      //启动工作者线程
      Thread thread = new Thread(worker);
      thread.start();
    }
  }

public void execute(Job job) {
    if (job != null) {
    //根据线程的"等待/通知机制"这里必须对jobs加锁
      synchronized (jobs) {
        jobs.addLast(job);
        jobs.notify();
      }
    }
  }
  //关闭线程池即关闭每个工作者线程
   public void shutdown() {
    for (Worker w : workers) {
      w.shutdown();
    }
  }
   //增加工作者线程
    public void addWorkers(int num) {
    //加锁,防止该线程还么增加完成而下个线程继续增加导致工作者线程超过最大值
    synchronized (jobs) {
      if (num + this.workerNum > MAX_WORKER_NUMBERS) {
        num = MAX_WORKER_NUMBERS - this.workerNum;
      }
      initializeWorkers(num);
      this.workerNum += num;
    }
  }
  //减少工作者线程
public void removeWorker(int num) {
    synchronized (jobs) {
    if(num>=this.workerNum){
         throw new IllegalArgumentException("超过了已有的线程数量");
         }
      for (int i = 0; i < num; i++) {
        Worker worker = workers.get(i);
        if (worker != null) {
        //关闭该线程并从列表中移除
          worker.shutdown();
          workers.remove(i);
        }
      }
      this.workerNum -= num;
    }
  }

public int getJobSize() {
    // TODO Auto-generated method stub
    return workers.size();
  }
//定义工作者线程
class Worker implements Runnable {
    // 表示是否运行该worker
    private volatile boolean running = true;

    public void run() {
      while (running) {
        Job job = null;
        //线程的等待/通知机制
        synchronized (jobs) {
          if (jobs.isEmpty()) {
            try {
              jobs.wait();//线程等待唤醒
            } catch (InterruptedException e) {
              //感知到外部对该线程的中断操作,返回
              Thread.currentThread().interrupt();
              return;
            }
          }
          // 取出一个job
          job = jobs.removeFirst();
        }
        //执行job
        if (job != null) {
          job.run();
        }
      }
    }

    // 终止该线程
    public void shutdown() {
      running = false;
    }
  }
}

从线程池的实现中可以看出,当客户端调用execute(Job)方法时,会不断地向任务列表jobs中添加Job,而每个工作者线程会不读的从jobs上获取Job来执行,当jobs为空时,工作者线程进入WAITING状态。

当添加一个Job后,对工作队列jobs调用其notify()方法来唤醒一个工作者线程。此处我们不调用notifyAll(),避免将等待队列中的线程全部移动到阻塞队列中而造成资源浪费。

线程池的本质就是使用了一个线程安全的工作队列连接工作者线程和客户端线程。客户端线程把任务放入工作队列后便返回,而工作者线程则不端的从工作队列中取出工作并执行。当工作队列为空时,工作者线程进入WAITING状态,当有客户端发送任务过来后会通过任意一个工作者线程,随着大量任务的提交,更多的工作者线程被唤醒。

参考: 《java并发编程的艺术》 方腾飞

您可能感兴趣的文章:

  • java中通用的线程池实例代码
  • Java 线程池详解及实例代码
  • 四种Java线程池用法解析
  • Java 线程池ExecutorService详解及实例代码
  • 深入java线程池的使用详解
  • 详谈Java几种线程池类型介绍及使用方法
  • java线程池:获取运行线程数并控制线程启动速度的方法
  • 支持生产阻塞的Java线程池
  • Java Socket编程实例(三)- TCP服务端线程池
  • Java线程池的几种实现方法和区别介绍
(0)

相关推荐

  • 深入java线程池的使用详解

    在Java 5.0之前启动一个任务是通过调用Thread类的start()方法来实现的,任务的提于交和执行是同时进行的,如果你想对任务的执行进行调度或是控制 同时执行的线程数量就需要额外编写代码来完成.5.0里提供了一个新的任务执行架构使你可以轻松地调度和控制任务的执行,并且可以建立一个类似数据库连接 池的线程池来执行任务.这个架构主要有三个接口和其相应的具体类组成.这三个接口是Executor, ExecutorService.ScheduledExecutorService,让我们先用一个图

  • Java 线程池ExecutorService详解及实例代码

    Java 线程池ExecutorService 1.线程池 1.1什么情况下使用线程池 单个任务处理的时间比较短. 将需处理的任务的数量大. 1.2使用线程池的好处 减少在创建和销毁线程上所花的时间以及系统资源的开销. 如果不使用线程池,有可能造成系统创建大量线程而导致消耗系统内存以及"过度切换"; 2.ExecutorService和Executors 2.1简介 ExecutorService是一个接口,继承了Executor, public interface ExecutorS

  • Java Socket编程实例(三)- TCP服务端线程池

    一.服务端回传服务类: import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.util.logging.Level; import java.util.logging.Logger; public class EchoProtocol implements Runnable { private static f

  • java线程池:获取运行线程数并控制线程启动速度的方法

    在java里, 我们可以使用Executors.newFixedThreadPool 来创建线程池, 然后就可以不停的创建新任务,并用线程池来执行了. 在提交任务时,如果线程池已经被占满,任务会进到一个队列里等待执行. 这种机制在一些特定情况下会有些问题.今天我就遇到一种情况:创建线程比线程执行的速度要快的多,而且单个线程占用的内存又多,所以很快内存就爆了. 想了一个办法,就是在提交任务之前,先检查目前正在执行的线程数目,只有没把线程池占满的时候在去提交任务. 代码很简单: int thread

  • java中通用的线程池实例代码

    复制代码 代码如下: package com.smart.frame.task.autoTask; import java.util.Collection;import java.util.Vector; /** * 任务分发器 */public class TaskManage extends Thread{    protected Vector<Runnable> tasks = new Vector<Runnable>();    protected boolean run

  • Java 线程池详解及实例代码

    线程池的技术背景 在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源.在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收. 所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁.如何利用已有对象来服务就是一个需要解决的关键问题,其实这就是一些"池化资源"技术产生的原因. 例如Android中常见到的很多通用组件一般都离不开"池"的概念,如各种图片

  • Java线程池的几种实现方法和区别介绍

    Java线程池的几种实现方法和区别介绍 import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.E

  • 详谈Java几种线程池类型介绍及使用方法

    一.线程池使用场景 •单个任务处理时间短 •将需处理的任务数量大 二.使用Java线程池好处 1.使用new Thread()创建线程的弊端: •每次通过new Thread()创建对象性能不佳. •线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom. •缺乏更多功能,如定时执行.定期执行.线程中断. 2.使用Java线程池的好处: •重用存在的线程,减少对象创建.消亡的开销,提升性能. •可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞

  • 支持生产阻塞的Java线程池

    通常来说,生产任务的速度要大于消费的速度.一个细节问题是,队列长度,以及如何匹配生产和消费的速度. 一个典型的生产者-消费者模型如下:   在并发环境下利用J.U.C提供的Queue实现可以很方便地保证生产和消费过程中的线程安全.这里需要注意的是,Queue必须设置初始容量,防止生产者生产过快导致队列长度暴涨,最终触发OutOfMemory. 对于一般的生产快于消费的情况.当队列已满时,我们并不希望有任何任务被忽略或得不到执行,此时生产者可以等待片刻再提交任务,更好的做法是,把生产者阻塞在提交任

  • 四种Java线程池用法解析

    本文为大家分析四种Java线程池用法,供大家参考,具体内容如下 1.new Thread的弊端 执行一个异步任务你还只是如下new Thread吗? new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub } } ).start(); 那你就out太多了,new Thread的弊端如下: a. 每次new Thread新建对象性能差. b. 线程缺乏统一管理,可能无限

随机推荐