6、使用线程池ExecuteService
线程池为线程生命周期开销问题和资源不足问题提供了解决方案。通过对多个任务重用线程,线程创建的开销被分摊到多个任务上。其好处是,因为在请求到达时线程已经存在,所以无意中也消除了线程创建所带来的延迟。这样就可以立即为请求服务,使应用程序响应更快。而且,通过适当地调整线程池中的线程数目,也就是当请求的数目超过某个阈值时,就可以强制其他任何新到的请求一直等待,直到获得一个线程来处理位置,从而防止资源不足。
线程池是管理线程的高级技术,通常它提供了如下几个功能
- 通过对线程的管理,更加合理的调配资源。通常,线程池里维护着一组空闲线程,并向外提供,根据系统繁忙程度动态增加或减少空闲线程的数量。比较高级的还提供了自动检测异常的功能。
- 通过维护线程池中的既存线程,可以节省创建线程的开销,尤其是对WebServer这类处理频繁,而处理过程又比较快的程序,创建线程的开销不能忽略。
java.util.concurrent提供了互斥、信号量、诸如在并发访问下执行得很好的队列和散列表之类的集合类及几个工作队列实现。该包中的Excutors类是一种有效的、广泛使用的以工作队列为基础的线程池的正确实现。个人无须尝试编写自己的线程池,容易出错。
线程池的创建很简单,只需要使用Excutors.newFixedThreadPool(i)函数即可创建大小为i的线程池,实例属于ExecutorService类型。然后可以调用该类的execute()函数来启动一个线程,输入的参数为线程的变量。
下面是一个简单的例子,使用了2个大小的线程池来处理100个线程。
注意:线程池必须使用shutdown来显示关闭,否则主线程无法退出。
shutdown不会阻塞主线程
package org.test.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author Administrator
*
* 使用两个线程池来处理100个线程
*
*/
public class TestExecuteService {
public static void main(String[] args) {
ExecutorService exec = Executors.newFixedThreadPool(2);
for(int i = 0;i < 100;i++){
//定义一个内部类
Runnable run = new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
long time = (long) (Math.random() * 1000);
System.out.println("休眠"+time);
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
exec.execute(run);
}
//必须关闭线程池
exec.shutdown();
}
}
在循环过程中,对等待线程池中有空闲的线程,所以主线程会阻塞,为了解决这个问题,一般启动一个线程来做for循环,就是为了避免由于线程池满了造成主线程阻塞。代码如下所示:
package org.test.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author Administrator
*
* 使用两个线程池来处理100个线程
*/
public class TestExecuteService {
public static void main(String[] args) {
new TestExecuteService().testExec(2,100);
new TestExecuteService().testForExec(2,100);
}
/**
* 可能会出现线程阻塞
* */
private void testExec(int poolSize,int threadSize) {
ExecutorService exec = Executors.newFixedThreadPool(poolSize);
for(int i = 0;i < threadSize;i++){
//定义一个内部类
Runnable run = new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
long time = (long) (Math.random() * 1000);
System.out.println("休眠"+time);
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
exec.execute(run);
}
//必须关闭线程池
exec.shutdown();
}
/**
* 用一个线程来处理for循环
* */
private void testForExec(final int poolSize,final int threadSize){
//创建内部类
class Test extends Thread{
@Override
public void run() {
ExecutorService exec = Executors.newFixedThreadPool(poolSize);
for (int i = 0; i < threadSize; i++) {
//定义一个内部类
Runnable run = new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
long time = (long) (Math.random() * 1000);
System.out.println("休眠 "+time);
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
exec.execute(run);
}
//必须显示关闭线程池
exec.shutdown();
}
}
new Test().start();
}
}
在我们编写基于Socket的服务器程序时,通常的做法是,每连接一个客户端都为它创建一个新的线程,客户端离开后再销毁该线程。但是如果频繁的创建多个线程并销毁,对系统资源会造成很大的浪费。然而使用线程池技术就可以大大减少程序的创建和销毁次数,提高服务器的工作效率。