1.线程
创建一个线程有两种方法,一种是继承Thread类,一种是时间Runnable接口。
当我们Thread t = new Thread()的时候,Thread类内部会调用init方法
init(null, null, "Thread-" + nextThreadNum(), 0);
init方法传入空的线程组,Runnable对象,和默认的线程名称,即以 thread- 开头,从0开始依次递增的线程名称
private void init(ThreadGroup g, Runnable target, String name,long stackSize) {
init(g, target, name, stackSize, null, true);
}
通过方法重载,如果线程名称为null抛出异常。
如果传入的线程组g是null,Thread会默认获取父线程的线程组作为该线程的线程组。
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
if (security != null) {
g = security.getThreadGroup();
}
if (g == null) {
g = parent.getThreadGroup();
}
}
。。。
}
在我们调用start()方法时,如果传递的Runable为空,或者没有复写run方法,这个Thread不会调用任何东西。
如果传递了Runnable的实例,或者复写了run方法,则会执行逻辑。
@Override public void run() { if (target != null) { target.run(); } }
参数stacksize代表该线程占用stack大小,如果没有指定,默认为0,0代表忽略该参数。在一些操作系统中该参数无效。
2.Thread的一些API
join:Thread类中的join方法的主要作用就是同步,它可以使得线程之间的并行执行变为串行执行
如下代码:
public static void main(String[] args) { Thread t = new Thread(){ @Override public void run() { IntStream.range(1,50).forEach(i -> System.out.println(Thread.currentThread().getName()+"---"+i)); } }; t.start(); // try { // t.join(); // } catch (InterruptedException e) { // e.printStackTrace(); // } Optional.of("main bengin").ifPresent(System.out::println); IntStream.range(1,50).forEach(i -> System.out.println(Thread.currentThread().getName()+"---"+i)); }
创建一个线程t,和main线程做同样的事情。当把 t.join()方法注释时,程序的结果是两个线程相互争抢执行权,结果中thread-0和main线程会交替打印输出。而当使用join()后,程序会等t线程执行结束后,再去执行main线程的内容。
interrupt:Thread的interrupt方法是设置线程的中断标记。
当线程调用sleep,wait,join方法时,线程处于阻塞状态。当对处于阻塞状态的线程调用interrupt方法时会抛出InterruptException异常,而这个异常会清除中断标记。
public class Demo { public static class Demo2 extends Thread{ @Override public void run() { while (true){ try { Thread.sleep(1); } catch (InterruptedException e) { break; } } } } public static void main(String[] args) { Demo2 d = new Demo2(); d.start(); try { Thread.sleep(3_000); } catch (InterruptedException e) { e.printStackTrace(); } d.interrupt(); } }
如上代码中启动一个d线程,run方法中调用sleep方法让它每次执行都是阻塞状态。
让主线程休眠3秒后,调用d线程的interrupt方法,此时sleep方法会捕获到InterruptException异常。
3.线程池:
线程池的作用是管理线程资源,可以减少系统频繁的创建和销毁线程所消耗的时间。而当系统中创建了太多的线程在同时运行时,会给系统带来很大的负担。线程池可以解决这些问题。
线程池的概念是Executor这个接口,具体实现为ThreadPoolExecutor类。此类中包括4个构造函数:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler); }
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, defaultHandler); }
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) { this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), handler); }
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.acc = System.getSecurityManager() == null ? null : AccessController.getContext(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
int corePoolSize:该线程池中最大的核心线程数
核心线程:
线程池新建线程的时候,如果当前线程总数小于corePoolSize,则新建的是核心线程,如果超过corePoolSize,则新建的是非核心线程
核心线程默认情况下会一直存活在线程池中,即使这个核心线程啥也不干(闲置状态)。
如果指定ThreadPoolExecutor的allowCoreThreadTimeOut这个属性为true,那么核心线程如果不干活(闲置状态)的话,超过一定时间(时长下面参数决定),就会被销毁掉
int maximumPoolSize:线程总数的最大数(核心线程+非核心线程)
long keepAliveTime:该线程池中非核心线程闲置超时时长
一个非核心线程,如果不干活(闲置状态)的时长超过这个参数所设定的时长,就会被销毁掉
如果设置allowCoreThreadTimeOut = true,则会作用于核心线程
TimeUnit unit:有7种取值,在TimeUnit类中有7种静态属性
TimeUnit.DAYS; 天 TimeUnit.HOURS; 小时 TimeUnit.MINUTES; 分钟 TimeUnit.SECONDS; 秒 TimeUnit.MILLISECONDS; 毫秒 TimeUnit.MICROSECONDS; 微妙 TimeUnit.NANOSECONDS; 纳秒
BlockingQueue<Runnable> workQueue:一个阻塞队列,用来存储等待执行的任务
常用的workQueue类型:
SynchronousQueue:这个队列接收到任务的时候,会直接提交给线程处理,而不保留它,如果所有线程都在工作怎么办?那就新建一个线程来处理这个任务!所以为了保证不出现<线程数达到了maximumPoolSize而不能新建线程>的错误,使用这个类型队列的时候,maximumPoolSize一般指定成Integer.MAX_VALUE,即无限大
LinkedBlockingQueue:是一个由链表实现的有界队列阻塞队列,但大小默认值为Integer.MAX_VALUE。这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了maximumPoolSize的设定失效,因为总线程数永远不会超过corePoolSize
ArrayBlockingQueue:可以限定队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则发生错误
DelayQueue:队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务
ThreadPoolExecutor的策略
上面介绍参数的时候其实已经说到了ThreadPoolExecutor执行的策略,这里给总结一下,当一个任务被添加进线程池时:
- 线程数量未达到corePoolSize,则新建一个线程(核心线程)执行任务
- 线程数量达到了corePoolsSize,则将任务移入队列等待
- 队列已满,新建线程(非核心线程)执行任务
- 队列已满,总线程数又达到了maximumPoolSize,就会由RejectedExecutionHandler抛出异常
常用的四种线程池:
如果不想手写一个线程池,可以用Executors提供的四种线程池,应用在一些常用的场景中。
CachedThreadPool()
可缓存线程池:
- 线程数无限制
- 有空闲线程则复用空闲线程,若无空闲线程则新建线程
- 一定程序减少频繁创建/销毁线程,减少系统开销
创建方法:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
代码: public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
FixedThreadPool()
定长线程池:
- 可控制线程最大并发数(同时执行的线程数)
- 超出的线程会在队列中等待
创建方法:
参数int nThreads就是最大线程数,即maximumPoolSize
ExecutorService es = Executors.newFixedThreadPool(int nThreads);
代码:
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
ScheduledThreadPool()
定长线程池:
- 支持定时及周期性任务执行。
创建方法:
//nThreads => 最大线程数即maximumPoolSize
ExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(int corePoolSize);
代码:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
//ScheduledThreadPoolExecutor():
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
SingleThreadExecutor()
单线程化的线程池:
- 有且仅有一个工作线程执行任务
- 所有任务按照指定顺序执行,即遵循队列的入队出队规则
创建方法:
ExecutorService singleThreadPool = Executors.newSingleThreadPool();
代码:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
Callable()和Runnable()
上面介绍完了几种线程池的创建,那么接下来我们要给线程池中的线程添加任务。
而在接口ExecutorService中,提供了四个添加任务的方法:
public void execute(Runnable command)
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
看方法可以得出,如果使用execute(Runnable command)方法,无法获取到返回值。而在一些需要获取返回结果的场景中,就需要调用submit方法从而获取线程执行完返回的结果。
参数Callable和Runnable:
@FunctionalInterface public interface Callable<V> { V call() throws Exception; }
@FunctionalInterface public interface Runnable { public abstract void run(); }
如上代码,这两个接口都被@FunctionInterface标记,即JDK8的新特性:函数式接口,即是指仅仅只包含一个抽象方法的接口。
(1)Callable规定的方法是call(),Runnable规定的方法是run()。其中Runnable可以提交给Thread来包装下,直接启动一个线程来执行,而Callable则一般都是提交给ExecuteService来执行。
(2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值得
(3)call方法可以抛出异常,run方法不可以
(4)运行Callable任务可以拿到一个Future对象
ExecutorService es = Executors.newFixedThreadPool(2); Future<?> hahaha = es.submit(new Runnable() { @Override public void run() { System.out.println("hahaha");//打印输出 } }); System.out.println(hahaha.get());//null Future<String> submit = es.submit(new Callable<String>() { @Override public String call() throws Exception { return "xixixi"; } }); System.out.println(submit.get());//打印输出 xixixi
Future:
public interface Future<V> {
如果任务还没开始,执行cancel(...)方法将返回false;如果任务已经启动,执行cancel(true)方法将以中断执行此任务线程的方式来试图停止任务,如果停止成功,返回true;当任务已经启动,执行cancel(false)方法将不会对正在执行的任务线程产生影响(让线程正常执行到完成),此时返回false;当任务已经完成,执行cancel(...)方法将返回false。mayInterruptRunning参数表示是否中断执行中的线程。
boolean cancel(boolean mayInterruptIfRunning);
如果任务完成前被取消,则返回true。
boolean isCancelled();
如果任务执行结束,无论是正常结束或是中途取消还是发生异常,都返回true。
boolean isDone();
获取异步执行的结果,如果没有结果,此方法会阻塞直到异步计算完成。
V get() throws InterruptedException, ExecutionException;
获取异步执行结果,如果没有结果可用,此方法会阻塞,但是会有时间限制,如果阻塞时间超过设定的timeout时间,该方法将抛出异常。
V get(long timeout, TimeUnit unit)throws InterruptedException, ExecutionException, TimeoutException;
}
通过方法分析可以得出Future提供了三种功能:
- 终端执行中的任务
- 判断任务是否完成
- 获取任务执行完成的结果
线程池的关闭:
线程池的关闭
ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:
- shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
- shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务