在Java1.5之前,实现多线程编程比较麻烦,需要自己启动线程,并关注同步资源,防止线程死锁等问题,在1.5版本之后引入了并行计算框架,大大简化了多线程开发.

我们知道线程有5个状态:新建状态(New),可运行状态(Runnable,也叫做运行状态),阻塞状态(Blocked),等待状态(Waiting),结束状态(Terminated),线程的状态只能由新建状态转变为运行状态后才可能被阻塞或者等待,最终终结.

不可能出现本末倒置的情况,比如想把一个结束状态的线程转变为新建状态,则会出现异常.例如下面代码会出现异常:

 import java.util.concurrent.TimeUnit;

 public class Client {
public static void main(String[] args) throws Exception {
// 创建一个线程,新建状态
Thread t = new Thread(new Runnable() {
public void run() {
System.out.println("线程在运行……");
}
});
// 运行状态
t.start();
// 是否是运行态,若不是则等待10毫秒
while (!t.getState().equals(Thread.State.TERMINATED)) {
TimeUnit.MILLISECONDS.sleep(10);
}
System.out.println(t.getState());//TERMINATED
// 直接由结束态转变为运行态
t.start();
}
}

会报如下错误:

线程在运行……
TERMINATED
Exception in thread "main" java.lang.IllegalThreadStateException
at java.lang.Thread.start(Unknown Source)
at cn.summerchill.test.Client.main(Client.java:20)

出现上面的异常就是不能从结束状态直接转变成可运行状态.

一个线程的运行时间分为三部分:T1为线程的启动时间,T2为线程的运行时间,T3为线程的销毁时间.如果一个线程不能被重复使用,每次创建一个线程都需要经过启动,运行,销毁这三个过程,那么这势必会增大系统的响应时间,有没有一个更好的方法降低线程的运行时间?

T2是无法避免的,只有通过代码优化缩短线程的运行时间.

T1和T2都可以通过线程池ThreadPool来缩短时间,比如在容器或者系统启动时,创建足够多的线程,当容器或系统需要时直接从线程池中获取,运算出结果再把线程返回到线程池中---ExecutorService就是实现了线程池的执行器,看实例代码:

import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor; public class Client {
public static void main(String[] args) {
//2个线程的线程池
ThreadPoolExecutor es = (ThreadPoolExecutor)Executors.newFixedThreadPool(2);
//多次执行线程体
for (int i = 0; i < 4; i++) { es.submit(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
System.out.println(es.getPoolSize());
System.out.println(es.getCorePoolSize());
//关闭执行器
es.shutdown();
}
}

运行结果:

pool-1-thread-1
pool-1-thread-2
pool-1-thread-2
2
2
pool-1-thread-1

本次代码执行了4遍线程体,按照我们之前说的:一个线程不可能从结束状态转变为可运行状态,那为什么此处的2个线程可以反复使用呢?这就是我们要搞清楚的重点:

线程池的实现涉及以下三个名词:

(1)工作线程(Worker)

线程池中的线程,只有两个状态:可运行状态和等待状态,在没有任务的时候就处于等待状态,运行时可以循环的执行任务.

(2)任务接口(Task)

这是每个任务必须实现的接口,以供工作线程调度器调度,它主要规定了任务的入口,任务执行完的场景处理,任务的执行状态等...

这里有两种类型的任务,具有返回值或异常的Callable接口任务和无返回值并兼容旧版本的Runnable接口任务.

(3)任务队列(Work Queue)

用于存放等待处理的任务,一般是BlockingQueue的实现类,用来实现任务的排队处理.

***********分析源码****************

线程池的创建过程:

创建一个阻塞队列以容纳任务,在第一次执行任务时创建足够多的线程(不可超过许可线程数),并处理任务.之后每个工作线程自行从任务队列中获得任务,直到任务队列中的任务数量为0为止.

此时线程处于等待状态,一旦有任务再加入到队列中,即唤醒工作线程进行处理,实现线程的可复用性.

使用线程池减少的是线程的创建和销毁时间,这对于多线程来说非常有帮助.

我们常用的Servlet容器,每次请求处理的都是一个线程,如果不采用线程池技术,每次请求都会重新创建一个线程,这会导致系统的性能负荷加大,响应效率下降,降低了系统的友好性.

[改善Java代码]优先选择线程池的更多相关文章

  1. [改善Java代码]适时选择不同的线程池来实现

    Java的线程池实现从最根本上来说只有两个:ThreadPoolExecutor类和ScheduledThreadPoolExecutor类,这两个类还是父子关系,但是Java为了简化并行计算,还提供 ...

  2. [改善Java代码]优先使用整型池

    建议28: 优先使用整型池 看如下代码: public class Client { public static void main(String[] args) { Scanner input = ...

  3. [改善Java代码]适时选择getDeclaredxxx和getxxx

    Java的Class类提供了很多的getDeclaredxxx方法和getxxx方法,例如getDeclaredmethod和getMethod成对出现,getDeclaredConstructors ...

  4. [改善Java代码]自由选择字符串拼接方法

    对一个字符串拼接有三种方法:加号,contact方法,StringBuffer或者StringBuilder的append方法,其中加号是最常用的.其他两种方式偶尔会出现在一些开源项目中,那么这三者有 ...

  5. Java并发编程:线程池的使用

    Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...

  6. JAVA基础拾遗-论线程池的线程粒度划分与深浅放置

    摘要:多线程任务处理对提高性能很有帮助,在Java中提供的线程池也方便了对多线程任务的实现.使用它很简单,而如果进行了不正确的使用,那么代码将陷入一团乱麻.因此如何正确地使用它,如以下分享,这个技能你 ...

  7. Java并发编程:线程池的使用(转)

    Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...

  8. 深入理解Java自带的线程池和缓冲队列

    前言 线程池是什么 线程池的概念是初始化线程池时在池中创建空闲的线程,一但有工作任务,可直接使用线程池中的线程进行执行工作任务,任务执行完成后又返回线程池中成为空闲线程.使用线程池可以减少线程的创建和 ...

  9. Java并发编程:线程池的使用(转载)

    转载自:https://www.cnblogs.com/dolphin0520/p/3932921.html Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实 ...

随机推荐

  1. ResolverService跨子网的广播问题

    ResolverService在广播请求时,需要借助McastTransport,而McastTransport利用java.net.MulticastSocket进行收发,java.net.Mult ...

  2. 修改Map中确定key对应的value问题

    今天在码代码的时候出现一个没有预料的问题: 先看下面的代码: public static void main(String[] args) { String[] files=new String[]{ ...

  3. Spring MVC BeanNameUrlHandlerMapping example

    In Spring MVC, BeanNameUrlHandlerMapping is the default handler mapping mechanism, which maps URL re ...

  4. Linux 的进程组、会话、守护进程

    一.进程组ID 每个进程都属于一个进程组.每个进程组有一个领头进程.进程组是一个或多个进程的集合,通常它们与一组作业相关联,可以接受来自同一终端的各种信号.每个进程组都有唯一的进程组ID(整数,也可以 ...

  5. 被mysql中的wait_timeout坑了

    今天被mysql里的wait_timeout坑了         网上能搜到很多关于mysql中的wait_timeout相关的文章,但是大多数只是说明了他的作用,而且都说这个参数要配合那个inter ...

  6. [置顶] Quartz的DateBuilder详解

    DateBuilder类有两个方法: nextGivenMinuteDate和nextGivenSecondDate: Method: (a)  public static  Date   nextG ...

  7. PostgreSQL的schema信息,存储于何处

    查看schema信息: [pgsql@localhost bin]$ ./psql psql () Type "help" for help. pgsql=# create sch ...

  8. Android Developers:在命令行构建和运行

    使用Ant构建脚本构建你的应用程序有两种方式:一种用于测试/调试你的引用程序—debug模式—另一种用于构建你最终发布的包-release模式.无论你使用哪种方式构建你的应用程序,它必须在安装在模拟器 ...

  9. Android传感器编程带实例

    看了程序人生 网站的 编程高手的编程感悟 深有感触,好像也是一个android 程序员写的,推荐大家也看看.话不多说,还是言归正传吧. 一.前言 我很喜欢电脑,可是笔记本还是太大,笔记本电脑再小还是要 ...

  10. _vsnprintf 用法

    _vsnprintf,C语言库函数之一,属于可变参数.用于向字符串中打印数据.数据格式用户自定义. 头文件: #include <stdarg.h> 函数声明: int _vsnprint ...