Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具,它真正的线程池接口是ExecutorService。
使用线程池能够为了防止资源不足,因为频繁创建和销毁线程会消耗大量资源,尤其是当线程执行时间>线程创建时间+线程销毁时间,此时会堆积大量线程。Java中,创建线程池有四种方式,如下:
1)newCachedThreadPool()
创建一个可缓存线程池,线程池为无限大,如果线程池长度超过处理需要,可灵活回收空闲线程,即当执行当前任务时上一个任务已经完成,会复用执行上一个任务的线程,而不用每次新建线程;如果上一个线程没有结束则会新建线程。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CachedThreadPool {
private static ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
public static void main(String args[]) {
for (int i = 0; i < 10; i++) {
try {
if (i == 4) {
Thread.sleep(2000);
}
} catch (Exception e) {
e.getStackTrace();
}
cachedThreadPool.execute(new Runnable() {
public void run() {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.getStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在被执行");
}
});
}
}
/**
* 执行结果如下(console输出):
* pool-1-thread-1正在被执行
* pool-1-thread-4正在被执行
* pool-1-thread-3正在被执行
* pool-1-thread-2正在被执行
* pool-1-thread-4正在被执行
* pool-1-thread-1正在被执行
* pool-1-thread-3正在被执行
* pool-1-thread-2正在被执行
* pool-1-thread-5正在被执行
* pool-1-thread-6正在被执行
*/
}
在i=5的时候,需停留2秒,此时线程1-4都已经释放,所以1-4线程可以复用,这就是newCachedThreadPool()线程池的优点。
2)newFixedThreadPool()
可重用固定个数的线程池,当当前线程数大于总线程数时会进行等待,等待线程池内的线程执行完,相对来说占用内存少,因为如果等待线程数量过多,也是相对耗废资源的。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FixedThreadPool {
private static ExecutorService fixedThreadPool = Executors.newFixedThreadPool(4);
public static void main(String args[]) {
for (int i = 0; i < 10; i++) {
fixedThreadPool.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + "正在被执行");
try {
Thread.sleep(1000);
} catch (Exception e) {
e.getStackTrace();
}
}
});
}
}
}
线程池大小为4,所以如果线程执行数量超过4则会进行等待,所以newFixedThreadPool()的缺点也很明显,就是如果线程池数量设置明显过小,则会导致大量线程等待,造成资源浪费。
3)newSingleThreadExecutor()
单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO,优先级)执行,如果该线程异常结束,会有另一个线程取代它,保证顺序执行。
所以缺点也很明显,就是单线程执行,线程多,执行速度会比较慢。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingleThreadPool {
private static ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
public static void main(String args[]) {
for (int i = 0; i < 10; i++) {
final int index = i;
singleThreadPool.execute(new Runnable() {
public void run() {
try {
System.out.println(Thread.currentThread().getName() + "正在执行 index=" + index);
Thread.sleep(1000);
} catch (Exception e) {
e.getStackTrace();
}
}
});
}
}
/**
* 执行结果如下:
* pool-1-thread-1正在执行 index=0
* pool-1-thread-1正在执行 index=1
* pool-1-thread-1正在执行 index=2
* pool-1-thread-1正在执行 index=3
* pool-1-thread-1正在执行 index=4
* pool-1-thread-1正在执行 index=5
* pool-1-thread-1正在执行 index=6
* pool-1-thread-1正在执行 index=7
* pool-1-thread-1正在执行 index=8
* pool-1-thread-1正在执行 index=9
*
* 结论:所有的线程都是线程1来执行而且是有序执行,这就是单线程池!
*/
}
4)newScheduledThreadPool()
可重用固定个数的线程池,当前线程数大于总数则会进行等待,并且可以设置线程延迟执行时间。
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledThreadPool {
private static ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(4);
public static void main(String args[]) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmsssss");
for (int i = 0; i < 10; i++) {
System.out.println(i + "开始执行时间" + sdf.format(new Date()));
scheduledThreadPool.schedule(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + "正在执行 执行时间=" + sdf.format(new Date()));
}
}, 3, TimeUnit.SECONDS);
}
}
}
那么,使用线程池有没有什么风险呢?
1)死锁:任何的多线程都有可能发生死锁的风险(线程之间互相等待)
2)资源不足:线程池太大造成,正常来说合理创建线程池大小一般不会出现这个问题
3)线程泄漏和请求过载