前言:
本javaSE系列仅仅是个人在学习Think in java时做的一些笔记,写的很生硬,仅做个人复习用,不周之处欢迎指导。
1.1. 线程与进程
简而言之,一个程序至少有一个进程,一个进程至少有一个线程. 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率
主要差别:
它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程
1.2. 线程三种基本创建
线程最基本的类是Thread,继承了Runnable接口,Runnable接口是描述任务的接口,其核心是run方法。最简单的常有三种方式创建线程。
- 直接继承Thread类,重写run方法(因为Thread实现自Runnable接口)
package basic;
public class LiftOffThread extends Thread {
protected int countDown = 100000000;
private static int taskCount = 0;
private final int id = taskCount++;
public String status(){
return "#" + id + "(" + (countDown > 0 ? countDown : "LiftOff!") +"),";
}
@Override
public void run() {
while (countDown-- > 0) {
System.out.println(status());
Thread.yield();
}
}
}
LiftOffThread thread = new LiftOffThread();
Thread.start();
- 实现Runnable接口,实现run方法,添加实例进Thread的构造器或者与线程池配合。
package basic;
public class LiftOff implements Runnable {
protected int countDown = 10;
private static int taskCount = 0;
private final int id = taskCount++;
public LiftOff(){
}
public LiftOff(int countDown){
this.countDown = countDown;
}
public String status(){
return "#" + id + "(" + (countDown > 0 ? countDown : "LiftOff!") +"),";
}
@Override
public void run() {
while (countDown-- > 0) {
System.out.println(status());
Thread.yield();
}
}
}
new Thread(new LiftOff()).start();
- 实现Callable接口,与Future,线程池配合使用。
1.3. Executor与线程池
Executor家族.pngExecutor是concurrent包中的一个顶层接口,只有一个execute方法。目的在任务和客户端间提供一个间接层。ExecutorService是其子接口,也是很重要的具有生命周期的管理接口。
| void
| execute(
Runnablecommand)
在未来某个时间执行给定的命令。 |
execute方法只能接受Runnable任务。
事实上并没有直接表示几种线程池的类。ThreadPoolExecutor ScheduledThreadPoolExecutor(两个最终实现类)一般用来创建线程池,但并不直接使用,而是通过Executors类的静态方法创建(Executors静态方法内部使用了他们)。
Executors创建线程池方法:
返回值 | 方法名(参数) |
---|---|
static ExecutorService | newCachedThreadPool() |
static ExecutorService | newFixedThreadPool(int nThreads) |
static ScheduledExecutorService | newScheduledThreadPool(int corePoolSize) |
static ExecutorService | newSingleThreadExecutor() |
- CachedThreadPool会创建与当前所需数量相同的线程。往往是最常用的。
public void testCachedThreadPool(){
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++){
exec.execute(new LiftOff());
}
exec.shutdown();
exec.execute(new LiftOff());
System.out.println("over");
}
上图最后会抛出异常,因为shutDown方法后不能再提交新任务。
- FixedThreadPool会预先创建指定数目的线程
public void testFixedThreadPool(){
ExecutorService exec = Executors.newFixedThreadPool(3);
for (int i = 0; i < 3; i++){
exec.execute(new LiftOff());
}
}
- ScheduledThreadPool可对任务进行延期或定时工作。
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("delay 1 seconds, and excute every 3 seconds");
}
}, 1, 3, TimeUnit.SECONDS);
- SingleThreadExecutor创建单一线程池,就像是数量为1的FixedThrreadPool,如果向其提交了多个任务,那么他们会按顺序排队执行。
public void testSingleThreadExecutor(){
ExecutorService exec = Executors.newSingleThreadExecutor();
for (int i = 0; i < 3; i++){
exec.execute(new LiftOff());
}
}
如上图输出结果:#0(9),#0(8),#0(7),#0(6),#0(5),#0(4),#0(3),#0(2),#0(1),#0(LiftOff!),#1(9),#1(8),#1(7),#1(6),#1(5),#1(4),#1(3),#1(2),#1(1),#1(LiftOff!),#2(9),#2(8),#2(7),#2(6),#2(5),#2(4),#2(3),#2(2),#2(1),#2(LiftOff!),
ExecutorService接口常用方法:
返回值 | 方法(参数) |
---|---|
boolean |
isShutdown() 如果此执行程序已关闭,则返回 true 。 |
boolean |
isTerminated() 如果关闭后所有任务都已完成,则返回 true 。 |
void |
shutdown() 启动一次顺序关闭,执行以前提交的任务,但不接受新任务。 |
List< Runnable>
|
shutdownNow() 试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表。 |
<T> Future<T>
|
submit( Callable<T> task) 提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future。 |
Future<?>
|
submit( Runnabletask) 提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。 |
当然还有继承Executor的excute方法。
submit方法可以接收Callable也可以接受Runnable(其实在该方法内部,Runnable会被转成Callable)
注意:
-
shutdown()方法后面提交的任务“不允许接受”,在shutdown后提交任务,会抛出
RejectedExecutionException
的异常信息,也就是说必须在shutdown前submit,execute。 -
shutDownNow()方法等同于对线程池内所有线程使用interrupt()
1.4. Callable与Future家族
1.4.1. Callable
Runnnable与Callable的最大区别在与后者有返回值
public interface Callable<V> {
V call() throws Exception;
}
可以通过实现Callable接口,实现其中的call方法,定义一个任务,但是Thread的构造方法中没有Callable参数的。
而Runnable的实例可以通过Executors.callable(Runnable, T result)转成Callable。
想要将Callable任务交于线程,有两种方法:
- 通过FutureTask包装。
- 使用executorService的submit方法,同时会得到一个异步计算结果Future。
1.4.2. Future
Future<V>接口是用来获取异步计算结果的,说白了就是对具体的Runnable或者Callable对象任务执行的结果进行获取(get()),取消(cancel()),判断是否完成等操作。
接口源码:
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
重要方法:
get():获取异步计算结果,如果此时任务还未完成,则此方法阻塞直到任务完成。
get(long, TimeUnit):同上,只是阻塞等待有时间限制(以 unit为单位,timeout为值的时间)。
boolean isDone() :如果任务执行结束,无论是正常结束或是中途取消还是发生异常,都返回true。
boolean cancel(boolean mayInterruptRunning):尝试撤销任务,如果任务还未开始,结果为false;如果已经启动,执行cancel(true)则等同于interrupt尝试中断线程;当任务已经启动,执行cancel(false)方法将不会对正在执行的任务线程产生影响(让线程正常执行到完成),此时返回false;当任务已经完成,执行cancel(...)方法将返回false。mayInterruptRunning参数表示是否中断执行中的线程。
综上述,Future有4种作用:(1)撤销未开始任务,(2)等同interrupt尝试中断,(3)判断任务是否完成,(4)获取任务执行结果
1.4.3 RunnableFuture
RunnableFuture接口继承了Runnable和Future接口,官方说明为“作为Runnable的Future”,也即为Runnable提供一个可返回结果的方式。
1.4.3. FutureTask
Future只是抽象接口,而FutureTask则是其实现类。
FutureTask实际上实现了RunnableFuture接口,也即实现了Future和Runnable两个接口,因此他可提交给execute执行。其内部还有一个Callable成员变量,它有两种构造器,一种接收Runnable,一种接收Callable,当接收Runnable时,其实是将它转换成了Callable,赋给了Callable成员变量。
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
FutureTask非常适合用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
1.4.4. Callable两种使用
1.使用Callable+Future获取执行结果
ExecutorService exec = Executors.newCachedThreadPool();
ArrayList<Future<String>> results = new ArrayList<>();
for (int i = 5; i < 10; i++){
results.add(exec.submit(new TaskWithResult(i)));
}
for (Future<String> result:results){
System.out.println(result.get());
}
- 使用FutureTask+Callable获取结果
FutureTask<String> ft = new FutureTask<>(new TaskWithResult(10));
exec.submit(ft);
exec.shutdown();
try {
Thread.sleep(1000);
System.out.println("主线程在执行其他任务");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ft.get());
futuretask可以直接通过get获取结果,无需显示的使用future=exec.submit(…);
1.5. 一些简单概念
-
休眠
Thread :static void sleep(long millis) throws InterruptedException
TimeUnit.SECONDS.sleep(int )throws interruptedException
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。 -
优先级
可以通过设置优先级,让某些线程获得更多执行,但是并不是优先级低的线程就不执行,而且试图操作线程优先级通常是错误的行为。
Thread: void setPriority(int newPriority) 更改线程的优先级。 -
联合
Thread: public void join() throws InterruptedException
一个线程A可以在运行时调用线程B的join()方法,这时A会停下,等待B线程先完成,再继续运行。
join方法可以被中断,但是注意调用的是A.interrupted,此时A会重新获取运行权,B不再先运行。
此时dopey线程在使用sleeper.join时,使用dopey.interrupt打断了join,dopey重新获取运行权 -
让步
Thread: public static void yield() 建议其他线程可以运行,仅仅是建议,可能还是自己继续执行。不能依赖于yield()方法 -
后台线程
Thread: void setDaemon(boolean on) 将该线程标记为后台线程。
后台线程是指,在程序运行时在后台提供服务的线程,它并不是不可缺少的,所以当其他非后台线程结束时,会杀死所有后台线程,结束程序。
设置后台线程必须在线程开始前。
1.6. 异常逃逸
一旦异常逃出任务的run()方法,它就会向外传播到控制台,试图在线程执行的外面使用try,catch来捕捉是无用的。
public class ExceptionThread implements Runnable {
public void run(){
throw new RuntimeException();
}
public static void main(String[] args) {
// TODO Auto-generated method stub
try{
ExecutorService exec=Executors.newCachedThreadPool();
exec.execute(new ExceptionThread());
}catch(RuntimeException ue){
System.out.println("Exception has been handled");
}//无法通过try catch捕捉逃离run的异常
}
}
结果:
Exception in thread "pool-1-thread-1" java.lang.RuntimeException
at exceptionRunOut.ExceptionThread.run(ExceptionThread.java:8)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
如上,虽然使用try,catch试图捕捉exec.execute(…)的异常,但是由于线程的本质,根本不可能有用。此时需要使用UncaughtExceptionHandler。
网友评论