2013/6/2
刚刚开了一个博客,感觉自己搞IT一年多了,也没搞出个什么名堂来,所以前几天受统一战线的IT屌丝男启发,觉得应该开个博客总结总结,多写写技术博,一方面可以看看自己到底都学了些什么以后好有个检验自己收获的地方,一方面在写技术博总结的过程中,也可以发现原先一些自己错误的猜测和臆想,可以及时纠正免得一直让错误的思维误导自己,最后碰到和别人讨论的时候把错误的消息传递给别人,说起来,这种事好像干了不止一次了...o(╯□╰)o...。
额。。我这个人写东西的风格比较抽象。。。只是简单的把脑袋里冒出来的文字。。打印到前台来。。所以一般来说。如果有看官。。请见谅。。你懂的。。
那么就开始吧。。周末北京的天有点灰灰的。。正好适合呆在家里写博。。。第一篇我打算写个关于java线程池的东西,这个是我开始工作以后最先感兴趣的东西,为什么感兴趣有点忘记了。。总之今天就写他了 顺便更深的了解一下。
那么线程池是个什么东西咧?总之就是一个池子嘛,里头有很多名叫线程的小东西,他们一天到晚的忙着工作咧,你作为一个像上帝一样的存在,只要负责交代给他们要开始工作啦! 然后没事干的小线程就会屁颠屁颠的跑过来说接手你发布的工作,然后他就屁颠屁颠的去干活了,干完了他还想干,再来你发布工作的地方看看还有没有可以干的事情,有就继续干,没有就原地待命,等你下一次发布工作。
那么我们就自己搞出来这么一个小池子来,先来个简单的。。额。。首先想想需要哪些元素呢?
1.肯定得要有小线程嘛,没有工作的小东西怎么行,那么具体要有多少个呢?我就做3个小线程好了。
2我觉得吧,最好还有个主管之类的东西,这样你就可以不要自己去交代这个事情啦,找个人帮你干了多好。(这里我们就让线程池来充当这个角色好了)
3.最重要的差点忘记了。。作为一个线程池它是用来干什么的?干活的嘛! 我们是不是应该给一个需要干的活的清单这样才比较合理。?所以这里我们需要一个工作清单记着我们即将要做的工作。
元素都有了,就想演员都就位了一样,我们就要开拍了。那么我们要开始想象一下这个美妙的工作流程了,怎么把这些东西放在一起让他们工作起来呢?
1.首先得有初始化的过程,把20个小线程生出来,放到做好的池子里,然他们都先休息着。(这个很重要,得都先让他们歇着,要不然工作来了谁接?)
2.然后就是你派发任务,池子(现在他是主管)拿着拿到任务然后大喊小的们开始工作啦,闲着的线程就都醒来了,开始工作,完成工作后他就会等着下一个工作,就这么一直干下去,知道干完了手头的活,去清单哪里一看,咦?没活了,然后他就停下,等待下一次派发任务。
3.假设全部活都干完了。以后再也不会有活了。(程序停止了),那么我们实际应该让这个线程池停止工作,让后销毁所有的小线程。这样能够避免内存泄漏,但这个我们暂时就不做了。。咱们就一切从简。。后面再慢慢加。
好了。。恩。。大概我暂时也只能想到这些了。。那么我要去把我想到的东西实现出来了。。现在是13:58分。。朕先去敲活代码。。等下见。
<!-----------------------------------------------------------------我是分割君----------------------------------------------------------------------!>
- =让诸位见笑了。。现在是22:02,用了额。。总计9小时04分。。。搞了个简单的小池子。大概实现思路和上面的没有差异下面附上完成的代码
首先是我们的ThreadPool接口,定义了一些基本的线程池的行为。
ThreadPool:
package threadPool;
import java.util.List;
/**
*
* @description:线程池接口,定义一些简单的行为
* @createTime 2013 年 2013-6-2 下午2:49:08
* @author zhangjun
*/
public interface ThreadPool {
/**
*
* @description: 初始化环境,生产出20个小线程待命
* @createTime 2013 年 2013-6-2 下午2:54:34
* @author zhangjun
* @return void
*/
public void initThreadPool();
/**
*
* @description: 完成交代的工作
* @createTime 2013 年 2013-6-2 下午2:30:10
* @author zhangjun
* @return void
* @param work
*/
public void doJob(Runnable work);
/**
*
* @description: 完成大量交代工作
* @createTime 2013 年 2013-6-2 下午4:11:03
* @author zhangjun
* @return void
* @param works
*/
public void doJob(Runnable...works);
/**
*
* @description: 完成大量交代工作
* @createTime 2013 年 2013-6-2 下午4:11:03
* @author zhangjun
* @return void
* @param works
*/
public void doJob(List<Runnable> works);
/**
*
* @description: 杀死所有小线程!(主要是为了防止内存泄漏。。咱在这就不做了。。)
* @createTime 2013 年 2013-6-2 下午2:56:55
* @author zhangjun
* @return void
*/
public void destroyContext();
}
接着是ThreadPool的实现类SimpleThreadPool
SimpleThreadPool:
package threadPool;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
*
* @description: 自定义线程池
* @createTime 2013 年 2013-6-2 下午2:15:43
* @author zhangjun
*/
public class SimpleThreadPool implements ThreadPool{
/**
* 这里处于概念考虑 线程池就应该是单例的,要不然池子做那么多干什么,浪费资源,又不能提高效率.
*/
private static SimpleThreadPool single = new SimpleThreadPool();
/**
* 需要干的活的清单
*/
public List<Runnable> workList = new ArrayList<Runnable>();
private SimpleThreadPool(){
initThreadPool();
}
public static SimpleThreadPool getInstance(){
return single;
}
@Override
public void initThreadPool() {
//就做3个
for(int i = 0 ; i<3; i++ ){
SimpleThread thread = new SimpleThread();
//启动线程
thread.start();
String threadStatu = thread.isAlive()?"激活":"过期";
System.out.println("创建线程:"+thread.getName()+",当期状态:"+threadStatu);
}
}
@Override
public void doJob(Runnable work) {
this.workList.add(work);
System.out.println("加入1个工作");
synchronized(workList){
workList.notify();
}
}
@Override
public void doJob(Runnable ... works) {
this.workList.addAll(Arrays.asList(works));
System.out.println("加入"+works.length+"个工作");
doJob();
}
@Override
public void doJob(List<Runnable> works) {
this.workList.addAll(works);
System.out.println("加入"+works.size()+"个工作");
doJob();
}
private void doJob(){
//唤醒所有线程!开始工作
synchronized(workList){
workList.notifyAll();//小的们 开始干活啦
}
}
@Override
public void destroyContext() {
//we will do nothing here
}
/**
*
* @description:SimpleThreadPool里头放的小线程定义
* @createTime 2013 年 2013-6-2 下午3:39:23
* @author zhangjun
*/
private class SimpleThread extends Thread{
/**
* 重载run方法
*/
public void run() {
Runnable work = null;
while(true){
synchronized (workList) {
while(workList.size() == 0 ){
try {
workList.wait();//等着干活
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//有尚未完成的工作
work = workList.get(0);
workList.remove(work);//被领走的活需要从工作清单中删除
}
//多线程的优势就在这!
System.out.println("线程:"+this.getName()+" 开始工作");
//干活
work.run();
System.out.println("线程:"+this.getName()+" 结束工作");
}
}
}
}
然后是我们的工作类Work,定义了一个简单的工作(输出工作自己的名字)
Work:
package threadPool;
public class Work implements Runnable {
private int index;
public Work(int index){
this.index = index;
}
@Override
public void run() {
System.out.println("我是工作,编号:"+this.index);
}
}
Tester:
package threadPool;
import java.util.ArrayList;
import java.util.List;
public class Tester {
public static void main(String[] args) {
SimpleThreadPool threadPool = SimpleThreadPool.getInstance();
List<Runnable> works = new ArrayList<Runnable>();
for(int i = 0;i<6;i++){
works.add(new Work(i));
}
threadPool.doJob(works);
}
}
弄了这么久才弄这么个东西出来,哈哈,略囧略囧。这个自己写的小池子经测试可行,总结一下:
1.做的时候并不知道没有理解通过notify(), wait()控制线程阻塞和唤醒的机制,想当然的写在同步方法之外,后来发现不行,会抛出IllegalMonitorStateException,即非法的监视器状态异常(有木有感觉神翻译。。)。究其原因在查看完别人的回答之后,还有我自己的猜测后我得出的结论是:
每个Object当被套在synchronized()中是jvm会设法为这个Object添加一个监视器即ObjectMonitor。当你在执行同步方法时执行了wait()方法阻塞当前线程,实际上你在监视器中注册了当前线程(可能就是一个set之类的数据结构,没有验证过),然后当在别的线程的同一个对象资源的同步方法中执行notify()方法时,会去这个Object的Monitor中唤醒一个阻塞的线程,当执行完当前的同步方法之后,这个被唤醒的线程就可以像其他正在竞争这个资源的别的线程一样,公平的去竞争这个资源(不能保证一定得标)。所以执行notify和wait方法,实际上都需要去对指定的Object的Monitor做特定的操作(wait位注册当前线程,notify位唤醒一个已注册的线程),只有这两个方法处在同步方法中,他们才能实际指导去对哪个Object的Monitor做操作。这样说大概就能把自己说服了。。
2.做的时候没想好,设计了一个主管的角色,然后写完主管的类又觉得这个类的只能完全可以由ThreadPool替代,否则,ThreadPool的功能就实在太少了。。。
3.一开始不知道Thread类的run方法需要自己重载,只是猜测Thread应该有个可以设置这个线程实际要执行的任务的方法,后来翻了下Thread的代码,发现只有构造方法可以给他的targett属性赋值。。然后run方法上注释了子类应当重写这个方法。。
然后就自己写了一个SimpleThread子类放在SimpleThreadPool里头作为内部类,为什么作为内部类呢?因为从建模关系的角度看,SimpleThreadPool和SimpleThread应该是组合关系,即这个SimpleThread只有这个SimpleThreadPool会使用,是整体和部分的关系,没有了SimpleThreadPool,SimpleThread也就没有了意义,这是设计的初衷。
4.测试的时候发现有的时候出来的数据很规律,总是一个线程开始工作,工作编号**,一个线程结束工作。不管测几次都是这样。。后来发现是同步方法的括号包的太大了。。把执行任务的代码也包进去了。。晕。。关于这个同步方法的括号问题,原则应当是包括操作共享资源的部分,能小尽量小,这样子一方面是能够真正体现出多线程的优势,另一方面是少包点,可以尽量减少死锁的几率。比如说你的一个Object的同步方法,不小心包到了另外一个共享资源,那么就有个可能在竞争这个共享资源的地方出现死锁。
以上就是今天的收获了。这个还只是刚刚开始。我们只是大致了解了一下线程池的工作机制,下次我们需要跟进jdk源代码看看jdk中的线程池是怎么实现的,之后看看别的框架中是怎么实现线程池的。
———写快乐无厘头的技术博!