如何创建一个线程(两种方式)
方式一:继承Thread类,覆写run方法
public class Test {
public static void main(String[] args) {
Thread thread = new ThreadDemo();
thread.start();
}
private static class ThreadDemo extends Thread {
@Override
public void run() {
System.out.println("This is a thread :" + Thread.currentThread().getId());
}
}
}
方式二:实现Runnable接口(推荐使用这种方式,扩展性更强)
public class Test {
public static void main(String[] args) {
Thread thread = new Thread(new Task());
thread.start();
}
private static class Task implements Runnable {
@Override
public void run() {
System.out.println("This is a thread :" + Thread.currentThread().getId());
}
}
}
为什么要使用线程池?
使用线程池,可以大大提高任务处理效率,因为线程的创建与销毁都需要消耗一定的资源,如果频繁的创建与销毁线程,不仅损耗可观的系统资源,而且响应慢;但是如果我们预先初始化一定数量线程,并且在使用完的时候不立即销毁(保存一定时间),这样就可以高响应外部任务(不需要等待),并且大大提高线程利用率(同数据库连接池一个道理);
说了线程池的好处,我们如何使用线程池呢?(需要我们自己写一个?)
嘿嘿,真不用,java已经给我们提供了一个非常棒的线程池工具(Executors),使用它你可以非常简单的控制,管理线程;
下面我们看下Excutors中的几个常用方法:
// 创建具有固定数量的线程池(线程池中线程数量始终固定为nThreads)
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
// 创建只有一个线程的线程池(其实就是Threads退化为1)
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
// 对于每一个任务总是用一个线程去执行(不缓冲),并且空闲线程自动保留60秒
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
发现没?其实这些方法都是封装ThreadPoolExecutor类而已,那我们现在来看看ThreadPoolExecutor类的构造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
重点关注这几个参数:
corePoolSize: | 核心线程数(当线程池中的线程数目小于corePoolSize时,对于提交过来的任务总是起一个新的线程去运行); |
maximumPoolSize: | 线程池允许的最大线程数; |
keepAliveTime: | 当线程数大于corePoolSize时,那些空闲线程保留时间; |
workQueue: | 工作队列(当线程池中的线程数目大于等于corePoolSize时,对于提交的任务总是放在工作对列,只有当工作队列满的时候,才会新起一个线程去执行) |
handler: | 异常处理策略(当工作队列已满,线程池数目也已达到maximumPoolSize,这时候对于提交过来任务的处理策略(有4种,稍后我会代码详述)); |
总结一下ThreadPoolExecutor处理任务的过程就是【important】:
若线程池中的线程数目小于corePoolSize,这时候总是用一个新的线程去执行提交的任务,而不缓存;
若线程池中的数目大于等于corePoolSize,这时候任务总是先被放入工作队列,只有当工作队列满的时候,才会新起一个线程去执行;
若工作队列已经满了,并且线程池中的数目也达到了maximumPoolSize,这时候对于提交过来的任务,只能执行异常策略了(可能被丢弃,也可能由提交任务的线程来执行(这种策略可以减缓任务提交速度));
// 上述逻辑相关代码:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
if (runState == RUNNING && workQueue.offer(command)) {
if (runState != RUNNING || poolSize == 0)
ensureQueuedTaskHandled(command);
}
else if (!addIfUnderMaximumPoolSize(command))
reject(command); // is shutdown or saturated
}
}
再补充几句:
如果使用了LinkedBlockingQueue(无界队列),由于队列始终不会满(除非内存资源耗尽),线程池中的线程数目会始终为corePoolSize(多出的任务会放在工作队列里),那么所以maximumPoolSize和keepAlive这两个参数实际上是没有意义的;
若使用ArrayBlockingQueue(有界队列,它最能体现线程池的工作流程,其余都是临界情况),你需要权衡线程数和工作队列大小;
至于SynchronousQueue,自己不是很清楚,T T:
最后我们详细说一下线程池的异常处理策略:
测试代码:
public class Test {
public static void main(String[] args) {
/**
* corePoolSize:2
* maximumPoolSize:3
* queueSize:1
* keepAlive:5s
*/
ExecutorService executors = new ThreadPoolExecutor(2, 3, 5, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(1),
new ThreadPoolExecutor.AbortPolicy());
System.out.println("main thread: " + Thread.currentThread().getId());
for (int i = 1; i <= 10; i++) {
final int taskId = i;
executors.execute(new Runnable() {
// 每一个task需要执行1min
public void run() {
System.out.println("start thread: " + Thread.currentThread().getId() + "--task id: " + taskId);
try {
TimeUnit.SECONDS.sleep(60);
} catch (InterruptedException e) {
}
System.out.println("end thread: " + Thread.currentThread().getId() + "--task id: " + taskId);
}
});
}
executors.shutdown();
}
}
1.AbortPolicy策略(这是默认策略,该策略会丢弃任务,并抛出异常)
我们看下执行结果
main thread: 1
start thread: 11--task id: 1
start thread: 12--task id: 2
start thread: 13--task id: 4
Exception in thread "main" java.util.concurrent.RejectedExecutionException
对于task1,task2 总是用一个新的线程去执行(此时线程数目已经达到corePoolSize),这时候又来了一个task3, 于是放入了工作队列(此时工作队列也满了),接着又来了一个task4(好吧,由于队列满了,只能再起一个线程去执行了,此时线程数已经达到maximumPoolSize),可谁知又来了一个task5(工作队列满了,线程数也已经达到maximumPoolSize),这时候只能丢弃任务并抛异常(RejectedExecutionException);
2.DiscardPolicy(丢弃任务但不抛异常)
我们看下执行结果:
main thread: 1
start thread: 11--task id: 1
start thread: 12--task id: 2
start thread: 13--task id: 4
end thread: 12--task id: 2
start thread: 12--task id: 3
end thread: 11--task id: 1
end thread: 13--task id: 4
end thread: 12--task id: 3
对于task1,task2用一个新的线程去执行,task3被放入队列,task4又新起一个线程去执行(因为工作队列已经满了),其余task都被丢弃(不抛异常),当task2执行完毕(有空闲线程了),于是从工作队列中取出task3去执行;
3.CallerRunsPolicy(由提交任务的线程去执行,该策略可以保证任务不被丢弃)
我们看下执行结果:
main thread: 1
start thread: 11--task id: 1
start thread: 12--task id: 2
start thread: 13--task id: 4
start thread: 1--task id: 5 ---> important
...... 省略
之前策略对于task5总是丢弃的,但是对于CallerRunsPolicy策略,它会让提交任务的线程去执行task(即这里的main thread)
4.DiscardOldestPolicy(删除队列中老的任务(即头元素))
我们看下执行结果:
main thread: 1
start thread: 11--task id: 1
start thread: 12--task id: 2
start thread: 13--task id: 4
end thread: 11--task id: 1
start thread: 11--task id: 10 ---> important
end thread: 12--task id: 2
end thread: 13--task id: 4
...... 省略
工作队列中任务总是被后来的任务所替代(队列大小为1),所以最后工作队列中只有最后一个任务task10
写到这里,我想大家对异常策略应该比较了解了!
参考文档:
http://dongxuan.iteye.com/blog/901689
作者写的很棒,看了获益匪浅,但是关于SynchronousQueue与作者理解的有所同...
分享到:
相关推荐
java线程池使用后到底要关闭吗 java线程池是一种高效的并发编程技术,可以帮助开发者更好地管理线程资源,提高系统的性能和可靠性。然而,在使用java线程池时,一个常见的问题是:使用完线程池后到底要不要关闭?...
Java线程池是一种高效管理线程的技术,它允许开发者预定义一组线程,根据任务的需要灵活调度,而不是每次需要执行任务时都创建新的线程。这种设计模式大大提高了系统的性能,减少了系统资源的消耗,特别是在高并发...
java线程池知识、
基于Java线程池技术实现Knock Knock游戏项目.zip 基于Java线程池技术实现Knock Knock游戏项目.zip 基于Java线程池技术实现Knock Knock游戏项目.zip 基于Java线程池技术实现Knock Knock游戏项目.zip 基于Java线程池...
讲述了java线程池的优点,参数,6种线程池的使用场景,线程池用到的handler,线程任务的提交方式等等。
### 自定义实现Java线程池 #### 一、概述 在深入探讨自定义Java线程池之前,我们先简要回顾一下线程池的基本概念及其重要性。线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动...
java技术学习——基于Java线程池技术实现Knock Knock游戏项目(包含服务端、客户端两部分) java技术学习——基于Java线程池技术实现Knock Knock游戏项目(包含服务端、客户端两部分) java技术学习——基于Java...
Java线程池是Java并发编程中的重要组成部分,它在多线程和高并发场景下扮演着关键角色。本文将深入探讨Java线程池的源码分析,并对比不同类型的线程池,以帮助开发者更好地理解和利用这一强大的工具。 首先,我们要...
Java线程池(ThreadPool)是Java并发编程中的一个重要概念,它可以帮助我们有效地管理和控制并发执行的任务,从而提高系统的效率和稳定性。线程池通过复用已存在的线程,避免了频繁创建和销毁线程带来的开销,同时也...
Java线程池是一种高效管理线程资源的工具,它的出现是为了应对多线程编程中频繁创建和销毁线程带来的性能开销以及资源消耗。在Java中,通过使用线程池,我们可以预先创建一定数量的线程,这些线程在空闲时可以被复用...
Java线程池是一种高效管理线程资源的工具,它通过维护一组可重用的线程来减少创建和销毁线程的开销。在Java中,`java.util.concurrent`包提供了`ExecutorService`接口和它的实现类,如`ThreadPoolExecutor`,来支持...
java 线程池jxl动态写入Excel
Java线程池是一种高效管理线程资源的技术,它允许开发者创建一组可重用的工作线程,从而避免频繁地创建和销毁线程带来的性能开销。线程池在Java中主要通过`java.util.concurrent`包中的`ExecutorService`接口及其...
NULL 博文链接:https://yulincqupt.iteye.com/blog/1673919
本文所提及的基于Java线程池技术的数据爬虫设计与实现,不仅涉及到了数据爬虫的原理和架构,还包括了多线程编程的知识点,以及线程池技术在数据爬虫中的具体应用。 首先,数据爬虫的基本原理是模拟用户的点击行为,...
Java线程池是Java并发编程中的重要组成部分,它允许开发者高效地管理多个并发执行的线程,有效地控制系统的资源消耗,提高系统性能和稳定性。在Java中,`java.util.concurrent`包提供了`ExecutorService`接口及其...
在Java开发中,处理大批量数据时,合理利用线程池可以显著提高程序的执行效率和资源利用率。本文将深入探讨如何在Java中使用线程池来查询大量数据,以及这样做的好处和实现方法。 首先,理解线程池的概念至关重要。...
"详细分析JAVA线程池" Java线程池是Java编程语言中的一种机制,用于管理和重用线程,以提高程序的性能和效率。下面是Java线程池的相关知识点: 1. 什么是线程池? 线程池是指在程序启动时创建的一组空闲线程,程序...
线程池里的线程等待等待队列里的线程执行,等待队列里的等待线程池里的完成,相互等待
### Java线程池详解 #### 引言 在现代计算机科学中,线程作为轻量级的进程,已经成为操作系统和应用程序提高并发性、优化资源利用的关键技术之一。线程池,作为线程管理的一种高级机制,更是现代软件工程中不可或...