在了解线程池之前,首先我们先了解什么是线程
在JAVA中,有两种方式创建线程
1、实现Runnable接口,实例化Thread;2 、继承Thread类,重写run()方法
区别:
Thread需要实现Runnable ,Runnable没有start()方法,只有Thread类中才有
(该段摘自百度)
http://wenku.baidu.com/link?url=Nxc165sfKCfnRuCnkZnNGNfF5ReG0tHU5cuYf-nrwhXnt-dX7ANAri-sjFUR_XfENJd-zBzKqIUvy5axb6V--vv8akTUV6AffaqGR5Xg1BC
在实际开发中一个多线程的操作很少使用Thread类,而是通过Runnable接口完成。
1、避免点继承的局限,一个类可以继承多个接口。2、适合于资源的共享
以常见的卖票为例
A: 通过Thread 类完成:
首先,定义一个窗口用于卖票的线程
public class MyThread extends Thread{
private int count =10;
private String name ;
public MyThread(String name){
this.name = name;
}
@Override
public void run() {
// TODO Auto-generated method stub
for(int i = 0;i<20;i++){
//i 表示该窗口卖出的张数对应的人的位数
if(this.count>0){
System.out.println(this.name+"買到第:"+count--+"張票");
}
}
}
}
接着,创建三个线程,同时卖票,我们看看会有什么样的结果,代码如下
public class TicketThread {
public static void main(String[] args) {
MyThread m1 = new MyThread("one");
MyThread m2 = new MyThread("two");
MyThread m3 = new MyThread("three");
m1.start();
m2.start();
m3.start();
}
}
三个线程开启后,控制台输出的结果如下:
two買到第:10張票
three買到第:10張票
one買到第:10張票
three買到第:9張票
two買到第:9張票
three買到第:8張票
one買到第:9張票
three買到第:7張票
two買到第:8張票
three買到第:6張票
one買到第:8張票
one買到第:7張票
three買到第:5張票
two買到第:7張票
three買到第:4張票
one買到第:6張票
three買到第:3張票
two買到第:6張票
three買到第:2張票
one買到第:5張票
three買到第:1張票
two買到第:5張票
one買到第:4張票
two買到第:4張票
one買到第:3張票
two買到第:3張票
one買到第:2張票
two買到第:2張票
one買到第:1張票
two買到第:1張票
很明显,三个线程都默认10张票归自己所有,当三个线程开启后,一个卖出了30张票,线程与线程之间并没有进行数据的交互
现在我们实现第二种方案:实现Runnable 接口
同样的,让我们的MyThread 实现Runnable 接口
public class MyThread implements Runnable{
private int count = 10;
@Override
public void run() {
// TODO Auto-generated method stub
for(int i = 10;i<20;i++){
synchronized (this)//同步
{
if(this.count>0){
System.out.println("買到第:"+count--+"張票");
}
}
}
}
}
相对应的操作类
public class TicketThread {
public static void main(String[] args) {
MyThread thread = new MyThread();
new Thread(thread).start();
new Thread(thread).start();
new Thread(thread).start();
}
}
再来看一下输出的结果:
買到第:10張票
買到第:9張票
買到第:8張票
買到第:7張票
買到第:6張票
買到第:5張票
買到第:4張票
買到第:3張票
買到第:2張票
買到第:1張票
---------------------------------------------------------------华丽的分割线----------------------------------------------------------------------
现在了解了Java中的线程,现在我们就进一步了解更难的知识
线程池
同样的,我们先了解一下什么是线程池
为什么要使用线程池:
1、开发者可以重复使用线程池中的线程,避免不必要的创建与销毁;
2、可以对线程进行一些简单的操作,例如时间的控制等;
3、控制线程的数量,避免大量线程并发时造成内存的损耗
一个线程执行任务所需要的时间为:创建时间+执行任务时间+销毁时间
若滥用线程,则容易导致不必要的浪费。线程池的使用正是减少了创建和销毁线程的时间,从而达到节省开销的目的。
现在,我们就来了解下线程池
线程池的构造方法:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,
TimeUnit unit, BlockingQueue<Runnable> workQueue)
-
用给定的初始参数和默认的线程工厂及被拒绝的执行处理程序创建新的
ThreadPoolExecutor。使用
-
corePoolSize
- 池中所保存的线程数,包括空闲线程。 -
maximumPoolSize
- 池中允许的最大线程数。 -
keepAliveTime
- 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。 -
unit
- keepAliveTime 参数的时间单位。 -
workQueue
- 执行前用于保持任务的队列。此队列仅保持由 execute 方法提交的 Runnable 任务。
抛出:
-
IllegalArgumentException
- 如果 corePoolSize 或 keepAliveTime 小于 0,或者 maximumPoolSize 小于等于 0,或者 corePoolSize 大于 maximumPoolSize。 -
NullPointerException
- 如果 workQueue 为 null
Executors
工厂方法之一比使用此通用构造方法方便得多。
-
参数:
-
参数:
-
corePoolSize
- 池中所保存的线程数,包括空闲线程。 -
maximumPoolSize
- 池中允许的最大线程数。 -
keepAliveTime
- 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。 -
unit
- keepAliveTime 参数的时间单位。 -
workQueue
- 执行前用于保持任务的队列。此队列仅保持由 execute 方法提交的 Runnable 任务。 -
threadFactory
- 执行程序创建新线程时使用的工厂。
抛出:
-
IllegalArgumentException
- 如果 corePoolSize 或 keepAliveTime 小于 0,或者 maximumPoolSize 小于等于 0,或者 corePoolSize 大于 maximumPoolSize。 -
NullPointerException
- 如果 workQueue 或 threadFactory 为 null。
-
参数:
-
corePoolSize
- 池中所保存的线程数,包括空闲线程。 -
maximumPoolSize
- 池中允许的最大线程数。 -
keepAliveTime
- 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。 -
unit
- keepAliveTime 参数的时间单位。 -
workQueue
- 执行前用于保持任务的队列。此队列仅由保持 execute 方法提交的 Runnable 任务。 -
handler
- 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。
抛出:
-
IllegalArgumentException
- 如果 corePoolSize 或 keepAliveTime 小于 0,或者 maximumPoolSize 小于等于 0,或者 corePoolSize 大于 maximumPoolSize。 -
NullPointerException
- 如果 workQueue 或 handler 为 null。
-
参数:
-
corePoolSize
- 池中所保存的线程数,包括空闲线程。 -
maximumPoolSize
- 池中允许的最大线程数。 -
keepAliveTime
- 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。 -
unit
- keepAliveTime 参数的时间单位。 -
workQueue
- 执行前用于保持任务的队列。此队列仅保持由 execute 方法提交的 Runnable 任务。 -
threadFactory
- 执行程序创建新线程时使用的工厂。 -
handler
- 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。
抛出:
-
IllegalArgumentException
- 如果 corePoolSize 或 keepAliveTime 小于 0,或者 maximumPoolSize 小于等于 0,或者 corePoolSize 大于 maximumPoolSize。 -
NullPointerException
- 如果 workQueue、 threadFactory 或 handler 为 null。
这是程池对拒绝任务的处理策略。一般是队列已满或者无法成功执行任务,这时ThreadPoolExecutor会调用handler的rejectedExecution方法来通知调用者
默认有四个拒绝策略
1、ThreadPoolExecutor.AbortPolicy() 直接抛出异常RejectedExecutionException
2、ThreadPoolExecutor.CallerRunsPolicy() 直接调用run方法并且阻塞执行
3、ThreadPoolExecutor.DiscardPolicy() 直接丢弃后来的任务
4、ThreadPoolExecutor.DiscardOldestPolicy() 丢弃在队列中队首的任务
官方定义的四种线程池
1、FixedThreadPool
线程池中的线程数量固定,并且任务队列的大小没有限制,并且在线程池中只有核心线程,故如果线程处于空闲状态也不会被回收
正因为其不会被回收,所以响应是最快的
2、CachedThreadPool
缓存线程池,线程池中没有核心线程,如果有空闲线程时,会利用其进行处理任务,所以任何添加进来的任务都会立刻执行,但前提是在缺省时间(60s)内,超过时长线程将会被移除线程池,该线程池适合大量的用时较少的异步任务;
3、ScheduledThreadPool
线程池中核心线程数固定,非核心线程数不固定,所以非核心线程空闲时会被收回;
4、SingleThreadExecutor
顾名思义,单线程池,线程池中只有一个核心线程,所有任务在同一线程中进行