创建多线程的方式有如下三种:
继承 Thread
实现 Runnable
实现 Callable
其中 Thread 其实本身就是以实现 Runnable 的方式创建线程
class Thread implements Runnable {
/* Make sure registerNatives is the first thing <clinit> does. */
private static native void registerNatives();
static {
registerNatives();
}
...
}
复制代码
相关知识点:
Runnable 和 Callable 的差异以及返回对象 Future
差异:
Runnable 在不需要获知线程执行结果的场景下使用,方法入口是 run()
Callable 在需要获知线程执行结果的场景下使用,方法入口是 call()
Future 是线程返回的对象,通过 get()获取返回结果信息
线程池
线程池实现类:ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
复制代码
参数说明:
corePoolSize:核心线程数量。当线程数少于 corePoolSize 的时候,直接创建新的线程,尽管其他线程是空闲的。当线程池中的线程数目达到 corePoolSize 后,就会把到达的任务放到缓存队列当中。
maximumPoolSize:线程池最大线程数。只有在缓冲队列满了之后才会申请超过核心线程数的线程。当线程数量大于最大线程数且阻塞队列满了这时候就会执行一些策略来响应该线程
workQueue:阻塞队列。存储等待执行的任务,会对线程池的运行产生很大的影响。当提交一个新的任务到线程池的时候,线程池会根据当前线程数量来选择不同的处理方式。
keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。
threadFactory:默认 Executors.defaultThreadFactory(),线程名称前缀赋值:"pool-**-thread-"
handler:拒绝策略
6.1. DiscardOldestPolicy 拒绝执行最老线程线程策略
6.2. AbortPolicy (默认)拒绝策略的处理方法就是抛出一个异常
6.3. CallerRunsPolicy 阻塞当前 Executor 线程,直至可以执行当前线程
6.4. DiscardPolicy 不做任何操作,新任务也不加入池中
ThreadLocal
给线程提供一个局部变量空间 map 对象,对当前线程赋值属性。
使用时需要注意,及时调用 remove 来回收,容易造成内存泄漏
如何使用线程池
4.1. 通过 Executors 创建线程池
4.1.1 newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
4.1.2 newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
4.1.3 newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行
4.1.4 newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
4.2. 获取线程执行结果
4.2.1. 使用ExecutorService
的invokeAll
// 创建一个线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
// 存储执行结果的List
List<Future<Object>> results = new ArrayList<Future<Object>>();
// 提交8个任务
for ( int i=0; i<8; i++ ) {
Future<Object> result = executorService.submit( new Callable<Object>(){
public Object call(){
int x = new Random().nextInt(1000);
Thread.sleep(x);
return x;
}
} );
// 将执行结果存入results中
results.add( result );
}
// 获取任务的返回结果
List<Future<String>> results = executorService.invokeAll( tasks );
// 获取8个任务的返回结果
for ( Future<Object> future : results) {
// 从future中取出执行结果(若尚未返回结果,则get方法被阻塞,直到结果被返回为止)
Object result = future.get();
System.out.println(result);
}
复制代码
缺点:如果有线程执行长的先入 list,会导致整个的等待时间不是最佳等待时间
4.2.2 使用CompletionService
CompletionService
内部维护了一个阻塞队列,只有执行完成的任务结果才会被放入该队列,这样就确保执行时间较短的任务率先被存入阻塞队列中
// 创建一个线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
// 实例化CompletionService
CompletionService completionService = new ExecutorCompletionService(executorService);
// 提交8个任务
for ( int i=0; i<8; i++ ) {
completionService.submit( new Callable<Object>(){
public Object call(){
int x = new Random().nextInt(1000);
Thread.sleep(x);
return x;
}
} );
// 将执行结果存入results中
results.add( result );
}
for ( int i=0; i<8; i++ ) {
// 获取包含返回结果的future对象(若整个阻塞队列中还没有一条线程返回结果,那么调用take将会被阻塞,当然你可以调用poll,不会被阻塞,若没有结果会返回null,poll和take返回正确的结果后会将该结果从队列中删除)
Object result = completionService.take().get();
System.out.println(result);
}
复制代码
评论