在开始介绍线程池的原理之前,我想问一下各位在使用线程的时候,有没有想过为什么一个thread只能start()一次呢?
调用两次start()??
如下代码。
public class Test {
public static class MyRunnable implements Runnable{
@Override
public void run() {
try {
System.out.println("start run");
}catch (Exception e){
e.printStackTrace();
}
}
}
public static void main(String[] agr) throws Exception{
Thread thread = new Thread(new MyRunnable());
thread.start();
//保证了上面的运行完毕
Thread.sleep(1000);
thread.start();
}
}
运行的时候报错:“java.lang.IllegalThreadStateException”
我就在想一个thread只能运行一次,想要复用也做不到,这也太浪费了吧。为什么当初设计Thread的时候,不考虑成:start运行完毕后,可以再次运行呢。
我想是因为,一个线程在无论在运行还是被阻塞都是有一个状态去表示的,如NEW 表示的是,至今尚未启动的线程的状态。RUNNABLE,可运行线程的线程状态。
如果你希望在一个线程在完成任务的时候,再次变成可以运行的状态NEW,那么这个NEW究竟是还没运行的第一次NEW,还是已经运行一次重新开始的NEW呢,说不清。一个完整的生命周期,如果能循环使用,那状态的标识就会失去意义了。
如何使线程复用?
说是复用,但我觉得用“一直用”更合适。假设这里有两个线程,thread1执行唱歌代码,thread2执行跳舞代码。那一般的做法就是开启两个线程去完成即可。
那现在如果有100个人唱歌,100个人跳舞,我们知道线程占用的资源是非常耗cpu的,并且线程的上下文切换也是很耗时的,这时候如果开启200个线程,对cpu的压力可想而知。所以我们需要想一个新的解决方法。
如果我只使用5个线程,这5个线程它一直在运行,就像这样:
public void run(){
while (true) {
dothing();
}
}
在dothing里面,5个线程去一个任务队列中获取这200个任务,然后执行了不就好了嘛!不错,这就是线程池的核心思想了。
那么现在需要解决的存放任务的队列,因为是多线程,所以要考虑到安全问题。我这里的实现是阻塞队列ArrayBlockingQueue,里面放着一个个任务。
ArrayBlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<Runnable>(100);
再来看看我们五个工人是怎么工作的:
public class WorkerThread extends Thread{
@Override
public void run(){
try {
while (true) {//一直跑!!!
/**去任务列表拿任务,如果拿到的是null,就休息5秒再去拿*/
Runnable runnable = workQueue.poll();
if(runnable==null){
System.out.println("等待任务中。。。");
Thread.sleep(5000);
}else{
/**执行任务,需要注意的是我用的是run,而不是start两者的区别不再多说*/
runnable.run();
/**执行完任务,随机休息几秒*/
Random random = new Random();
Thread.sleep(random.nextInt(5)*1000);
}
}
}catch (Exception e){
e.printStackTrace();
}
}
}
完整的代码:
import java.util.Random;
import java.util.Vector;
import java.util.concurrent.ArrayBlockingQueue;
/**
* Created by JinTX on 2017/10/31.
*/
public class MyThreadPool{
ArrayBlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<Runnable>(100);
public MyThreadPool(int count){
for(int i=0 ;i<count ;i++){
WorkerThread workerThread = new WorkerThread();
workerThread.start();
}
}
public class WorkerThread extends Thread{
@Override
public void run(){
try {
while (true) {
/**去任务列表拿任务,如果拿到的是null,就休息5秒再去拿*/
Runnable runnable = workQueue.poll();
if(runnable==null){
System.out.println("等待任务中。。。");
Thread.sleep(5000);
}else{
/**执行任务,需要注意的是我用的是run,而不是start两者的区别不在多说*/
runnable.run();
/**执行完任务,随机休息几秒*/
Random random = new Random();
Thread.sleep(random.nextInt(5)*1000);
}
}
}catch (Exception e){
e.printStackTrace();
}
}
}
/**生成消息的*/
public static class MissionRunnable implements Runnable{
public String job;
public MissionRunnable(String job){
this.job = job;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+""+job);
}
}
public static void main(String[] args) throws Exception{
MyThreadPool myThreadPool = new MyThreadPool(5);
for(int i=1 ; i<= 20;i++) {
MissionRunnable missionRunnable = new MissionRunnable(getJob(i));
myThreadPool.workQueue.put(missionRunnable);
}
}
public static String getJob(int index){
if(index%5==0) return "睡觉";
if(index%5==1) return "唱歌";
if(index%5==2) return "跳舞";
if(index%5==3) return "写字";
if(index%5==4) return "做家务";
return null;
}
}
执行的结果:
使用线程池的好处
1.减少在创建和销毁线程上所花的时间以及系统资源的开销
2.如不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存