java线程池和多线程的使用详解
Java 多线程和线程池使用
java多线程实现的几种方法
1.继承Thread类
继承Thread类,重写run方法,创建线程类对象调用start方法启动线程。
public class ThreadDemo {
/**
* 继承Thread类创建线程
*/
public static class MyThread extends Thread {
public MyThread() {
}
public MyThread(String threadName) {
this.setName(threadName);
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("继承Thread类创建线程:" + this.getName() + " " + i);
}
}
}
public static void main(String[] args) {
MyThread myThread1 = new MyThread("thread1");
MyThread myThread2 = new MyThread("thread2");
MyThread myThread3 = new MyThread("thread3");
myThread1.start();
myThread2.start();
myThread3.start();
}
}
程序三次运行结果(部分):


从以上运行结果可以看出线程的执行顺序是随机性的和代码的编写顺序无关。
2.实现Runnable接口
Runnable接口中只有一个抽象run方法。

实现Runnable接口重写run方法创建自己的线程,调用start方法启动线程。
public class ThreadDemo {
/**
* 实现Runnable创建线程
*/
public static class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("实现Runnable创建线程:" + Thread.currentThread().getName() + " " + i);
}
}
}
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread1 = new Thread(myRunnable);
Thread thread2 = new Thread(myRunnable);
Thread thread3 = new Thread(myRunnable);
thread1.setName("thread1");
thread2.setName("thread2");
thread3.setName("thread3");
thread1.start();
thread2.start();
thread3.start();
}
}
利用构造函数Thread(Runnable target) 启动线程,实际上Thread类也实现了Runnable接口,这样就可以将Thread对象交由其他线程调用run方法执行。
3.实现Callable接口

Runnable是出自jdk1.0,Callable出自jdk1.5,Callable相当于是对Runnable接口的增强,Runnable和Callable接口的区别就在于Callable接口有一个带有返回值的call方法,并且可以抛出异常。
public class ThreadDemo {
/**
* 实现Callable创建线程
*/
public static class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 10; i++) {
System.out.println("实现Callable创建线程:" + Thread.currentThread().getName() + " " + i);
}
return "线程执行完成";
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
Thread thread = new Thread(futureTask);
long start = System.currentTimeMillis();
thread.start();
String result = futureTask.get();
System.out.printf("线程执行结果【%s】,耗时:%d ms", result, System.currentTimeMillis() - start);
}
}
实现Callable接口,重写call方法,通过FutureTask交给线程执行,阻塞等待结果返回。
callable逻辑交由异步线程处理,主线程通过阻塞接受异步线程返回内容。
以下是根据JAVA FutureTask类源码注释翻译的内容:
FutureTask:一个可取消的异步的计算,这个类实现了runnable和Future接口。future提供了开始,取消计算,查询计算是否完成。get()方法将会是堵塞的,调用get()方法将会堵塞直到计算完成返回结果。一旦计算完成了就不可以再次开始或者取消,除非是调用runAndReset()方法。
FutureTask有NEW,COMPLETING,NORMAL,EXCEPTIONAL,CANCELLED,INTERRUPTING和INTERRUPTED七种状态,创建时状态为NEW,结果未赋值时更新为COMPLETING,结果赋值后更新为NORMAL,发生异常后会将状态置为EXCEPTIONAL,调用cancel方法后更新为CANCELLED或者INTERRUPTING状态。
Java线程池的使用
线程池的优点
线程池能够最大化资源的利用率,因为它可以重复利用已经创建的线程,避免了每次创建和销毁线程时的开销。
线程池可以根据需要动态地创建和销毁线程,从而更好地管理和调度线程,提高程序的性能和响应速度。
线程池可以更有效地处理大量并发请求,因为它可以将任务拆分成较小的部分,并发地提交给线程池中的线程处理,从而更快地完成任务。
线程池可以减少锁竞争等线程间相互干扰的问题,提高了程序的正确性和可靠性。
线程池可以提高程序的安全性,因为它可以限制同一时间只有一个线程可以执行某个任务,减少了多个线程同时执行同一个任务时可能发生的错误。
池化技术基本都具有提高程序的性能、响应速度、资源利用率、方便统一管理的优点
Java线程池
ThreadPoolExecutor 线程池位于 java.util.concurrent 包下,是 Java 中用于实现线程池的一种基础类。
Executor接口继承关系

Executor线程池相关顶级接口,它将任务的提交与任务的执行分离开来。
ExecutorService继承并扩展了Executor接口,提供了Runnable、FutureTask等主要线程实现接口扩展。
ThreadPoolExecutor是线程池的核心实现类,用来执行被提交的任务。
ScheduledExecutorService接口,是延时执行类任务的主要实现。
ThreadPoolExecutor构造方法参数详解
以下是ThreadPoolExecutor包含所有参数的构造方法:
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;
}
corePoolSize:线程池中核心线程的数量。线程池中会维护一个最小的线程数量,即使这些线程处理空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut。这里的最小线程数量即是corePoolSize。任务提交到线程池后,首先会检查当前线程数是否达到了corePoolSize,如果没有达到的话,则会创建一个新线程来处理这个任务。
maximumPoolSize:线程池中最大线程数量。当前线程数达到corePoolSize后,如果继续有任务被提交到线程池,会将任务缓存到工作队列中。如果队列也已满,则会去创建一个新线程来出来这个处理。线程池不会无限制的去创建新线程,它会有一个最大线程数量的限制,这个数量即由maximunPoolSize指定。
keepAliveTime:线程池中空闲线程的存活时间。一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么超过keepAliveTime设定的时间后,这个空闲线程会被销毁。
unit:空闲线程的存活时间单位。java.util.concurrent.TimeUnit 枚举类。
workQueue:工作队列。存放工作任务的队列,任务调度时会从该队列取出任务。JDK提供了四种实现队列:
- ArrayBlockingQueue
其是一个基于数组的阻塞队列,底层使用数组进行元素的存储。创建该阻塞队列实例需要指定队列容量,故其是一个有界队列。在并发控制层面,无论是入队还是出队操作,均使用同一个ReentrantLock可重入锁进行控制,换言之生产者线程与消费者线程间无法同时操作。
- LinkedBlockingQuene
其是一个基于链表的阻塞队列,底层使用链表进行元素的存储。该阻塞队列容量默认为 Integer.MAX_VALUE,即如果未显式设置队列容量时可以视为是一个无界队列;反之构建实例过程中指定队列容量,则其就是一个有界队列。在并发控制层面,其使用了两个ReentrantLock可重入锁来分别控制对入队、出队这两种类型的操作。使得生产者线程与消费者线程间可以同时操作提高效率。
- SynchronousQuene
其是一个同步队列。特别地是由于该队列没有容量无法存储元素,故生产者添加的数据会直接被消费者获取并且立刻消费。所以当生产者线程添加数据时,如果此时恰好有一个消费者已经准备好获取队头元素了,则会添加成功;否则要么添加失败返回false要么被阻塞。如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。
- PriorityBlockingQueue
线程安全版本的优先级队列PriorityBlockingQueue,其是一个支持优先级的无界阻塞队列。底层使用数组实现元素的存储、最小堆的表示。默认使用元素的自然排序,即要求元素实现Comparable接口;或者显式指定比较器Comparator。在并发控制层面,无论是入队还是出队操作,均使用同一个ReentrantLock可重入锁进行控制。值得一提的是,在创建该队列实例时虽然可以指定容量。但这并不是队列的最终容量,而只是该队列实例的初始容量。一旦后续过程队列容量不足,其会自动进行扩容。值得一提的是,为了保证同时只有一个线程进行扩容,其内部是通过CAS方式来实现的,而不是利用ReentrantLock可重入锁来控制。故PriorityBlockingQueue是一个无界队列。
以上队列相关描述取自:Java多线程之阻塞队列(知乎文章)
threadFactory:线程工厂。创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程。
handler:拒绝策略。当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,就会执行相应的拒绝策略。jdk中提供了4中拒绝策略:
- CallerRunsPolicy
只要线程池没有关闭,就交由调用方线程运行。谁提交任务谁来执行这个任务,即将任务执行放在提交的线程里面,减缓了线程的提交速度,相当于负反馈。在提交任务线程执行任务期间,线程池又可以执行完部分任务,从而腾出空间来。
使用场景:一般不允许失败的、对性能要求不高、并发量较小的场景下使用。
- AbortPolicy
直接丢弃任务,抛出RejectedExecutionException异常。
- DiscardPolicy
直接丢弃任务,什么都不做。
- DiscardOldestPolicy
弃任务队列中等待事件最长的,即最老的任务。
线程和线程池的使用规范
java提供了几种常见的线程池创建方式:
FixThreadPool 可重用固定线程池
线程池的大小一旦达到设定数量就会保持不变。
SingleThreadExcutor 单线程化的线程池
只有一个线程的线程池,任务按照提交的次序顺序执行的。
CachedThreadPool 可缓存线程池
此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。线程池的线程数可达到Integer.MAX_VALUE,即2147483647。
具体实现可自行查看JDK源码。
注意:
对于线程资源应通过线程池提供,避免自行显示创建线程。
虽然java提供了以上三种常见的线程池创建方式,但是以上三种队列,队列长度或者线程数的最大限制可达到Integer.MAX_VALUE,可能导致内存溢出,所以线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor的方式。
最新版阿里巴巴泰山版《Java开发手册》对线程和线程池的使用也提出了强制类型要求,如下图:

java线程池和多线程的使用详解的更多相关文章
- java 线程池、多线程实战(生产者消费者模型,1 vs 10) 附案例源码
导读 前二天写了一篇<Java 多线程并发编程>点我直达,放国庆,在家闲着没事,继续写剩下的东西,开干! 线程池 为什么要使用线程池 例如web服务器.数据库服务器.文件服务器或邮件服务器 ...
- Java线程同步的四种方式详解(建议收藏)
Java线程同步属于Java多线程与并发编程的核心点,需要重点掌握,下面我就来详解Java线程同步的4种主要的实现方式@mikechen 目录 什么是线程同步 线程同步的几种方式 1.使用sync ...
- 线程池的使用(ThreadPoolExecutor详解)
为什么要使用线程池? 线程是一个操作系统概念.操作系统负责这个线程的创建.挂起.运行.阻塞和终结操作.而操作系统创建线程.切换线程状态.终结线程都要进行CPU调度——这是一个耗费时间和系统资源的事情. ...
- java线程的五大状态,阻塞状态详解
一.状态简介 一个线程的生命周期里有五大状态,分别是: 新生 就绪 运行 死亡 运行后可能遇到的阻塞状态 二.相关方法 2.1 新生状态 Thread t = new Thread(); 正如我们前面 ...
- 线程池ThreadPoolExecutor、Executors参数详解与源代码分析
欢迎探讨,如有错误敬请指正 如需转载,请注明出处 http://www.cnblogs.com/nullzx/ 1. ThreadPoolExecutor数据成员 Private final Atom ...
- java线程池开启多线程
// //maximumPoolSize设置为2 ,拒绝策略为AbortPolic策略,直接抛出异常 ThreadPoolExecutor pool = new ThreadPoolExecutor( ...
- JAVA 线程池, 多线程
http://tutorials.jenkov.com/java-util-concurrent/executorservice.html http://howtodoinjava.com/core- ...
- java线程中yield(),sleep(),wait()区别详解
1.sleep() 使当前线程(即调用该方法的线程)暂停执行一段时间,让其他线程有机会继续执行,但它并不释放对象锁.也就是说如果有synchronized同步快,其他线程仍然不能访问共享数据.注意该方 ...
- java线程并发控制:ReentrantLock Condition使用详解
本文摘自:http://outofmemory.cn/java/java.util.concurrent/lock-reentrantlock-condition java的java.util.con ...
- Java线程池详解(二)
一.前言 在总结了线程池的一些原理及实现细节之后,产出了一篇文章:Java线程池详解(一),后面的(一)是在本文出现之后加上的,而本文就成了(二).因为在写完第一篇关于java线程池的文章之后,越发觉 ...
随机推荐
- selenium---xpath定位方法详解
Xpath定位 验证xpath写的是否正确: 1.打开浏览器检查页面,Ctrl+F,把路径输入进去,如果可以定位到的位置只有一个,说明是对的 2.在需要定位的页面,按F12后,切换至console ...
- Go_day03
Go基础语法 数组 数组是具有相同唯一类型的一组以编号且长度固定的数据项序列.类型可以是任意基本类型或者自定义类型. 数组一旦被定义后,大小不能被改变 func main() { //定义一个数组 v ...
- ClassLoader 双亲委派
一个程序有一个默认的appClassLoader.类不是由被调用者也不是被自身加载的,正常情况下是被默认的AppClassLoader加载的. System.out.println(test3.cla ...
- [转]常见的视频编码详解 Cinepak Codec by Radius
AVI所采用的压缩算法并无统一的标准.也就是说,同样是以AVI为后缀的视频文件,其采用的压缩算法可能不同,需要相应的解压软件才能识别和回放该AVI文件.除了Microsoft公司之外,其他公司也推出了 ...
- 《爆肝整理》保姆级系列教程-玩转Charles抓包神器教程(11)-Charles如何模拟弱网环境
1.前言 张三:"我写的软件好奇怪啊,在网络好的时候一点问题也没有,但是信号差的时候明显卡顿,看来我只能一直蹲在卫生间.电梯或者地铁(信号差)调bug了". Charles:&qu ...
- 记录一次重置数据库root用户的过程
服务器的mysql突然连接不上去了,密码也忘记了.只能重新设置密码了 1.使用如下指令打开mysql数据库配置文件(具体的文件路径以实际情况为准) vim /etc/my.cnf在虚拟机中直接输入即可 ...
- springboot实现短信验证码的发送
我使用的是阿里云短信服务 代码前的准备 1. 申请阿里云的短信服务 2. 添加签名,这里需要等待审核通过 3. 在模板管理设置自己的短信模板 下面添加模板,选择验证码,模板内容可以直接使用输入框内的示 ...
- 玩转Mybatis高级特性:让你的数据操作更上一层楼
目录 动态SQL 缓存机制 插件机制 自定义类型转换 总结 Mybatis高级特性能够帮助我们更加灵活地操作数据库,包括动态SQL.缓存机制.插件机制.自定义类型转换等.学习这些特性可以让我们更好地利 ...
- react中类组件、函数组件、state、单层遍历、多层遍历、先遍历后渲染、if-else、三目运算符
1.回顾 module.exports = { entry: {}, output: {}, plugins: [], module: {}, resolve: {}, devServe: {} } ...
- 逍遥自在学C语言 | 第一个C语言程序 九层之台起于垒土
一.人物简介 第一位闪亮登场,有请今后会一直教我们C语言的老师 -- 自在. 第二位上场的是和我们一起学习的小白程序猿 -- 逍遥. 二.C语言简介 C语言是一种高级语言,运行效率仅次于汇编,支持跨平 ...