线程-1. 线程基础

前言:

本javaSE系列仅仅是个人在学习Think in java时做的一些笔记,写的很生硬,仅做个人复习用,不周之处欢迎指导。

1.1. 线程与进程

简而言之,一个程序至少有一个进程,一个进程至少有一个线程. 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率

主要差别:
它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程

1.2. 线程三种基本创建

线程最基本的类是Thread,继承了Runnable接口,Runnable接口是描述任务的接口,其核心是run方法。最简单的常有三种方式创建线程。

  1. 直接继承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();
  1. 实现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();
  1. 实现Callable接口,与Future,线程池配合使用。

1.3. Executor与线程池

Executor家族.png

Executor是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()
  1. 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方法后不能再提交新任务

  1. FixedThreadPool会预先创建指定数目的线程
    public void testFixedThreadPool(){
        ExecutorService exec = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 3; i++){
            exec.execute(new LiftOff());
        }
    }
  1. ScheduledThreadPool可对任务进行延期或定时工作。
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
    System.out.println("delay 1 seconds, and excute every 3 seconds");
}
}, 1, 3, TimeUnit.SECONDS);
  1. 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() 试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表。
Future submit(Callable task) 提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future。
Future submit(Runnabletask) 提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。

当然还有继承Executor的excute方法。

submit方法可以接收Callable也可以接受Runnable(其实在该方法内部,Runnable会被转成Callable)

注意:

  1. shutdown()方法后面提交的任务“不允许接受”,在shutdown后提交任务,会抛出RejectedExecutionException的异常信息,也就是说必须在shutdown前submit,execute。

  2. shutDownNow()方法等同于对线程池内所有线程使用interrupt()

1.4. Callable与Future家族

1.4.1. Callable

Runnnable与Callable的最大区别在与后者有返回值

public interface Callable {
    V call() throws Exception;
}

可以通过实现Callable接口,实现其中的call方法,定义一个任务,但是Thread的构造方法中没有Callable参数的。
而Runnable的实例可以通过Executors.callable(Runnable, T result)转成Callable。

想要将Callable任务交于线程,有两种方法:

  1. 通过FutureTask包装。
  2. 使用executorService的submit方法,同时会得到一个异步计算结果Future。

1.4.2. Future

Future接口是用来获取异步计算结果的,说白了就是对具体的Runnable或者Callable对象任务执行的结果进行获取(get()),取消(cancel()),判断是否完成等操作。

接口源码:

public interface Future {
    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> results = new ArrayList<>();
for (int i = 5; i < 10; i++){
      results.add(exec.submit(new TaskWithResult(i)));
}
for (Future result:results){
     System.out.println(result.get());
}
  1. 使用FutureTask+Callable获取结果
FutureTask 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. 一些简单概念

  1. 休眠
    Thread :static void sleep(long millis) throws InterruptedException
    TimeUnit.SECONDS.sleep(int )throws interruptedException
    在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。

  2. 优先级
    可以通过设置优先级,让某些线程获得更多执行,但是并不是优先级低的线程就不执行,而且试图操作线程优先级通常是错误的行为。
    Thread: void setPriority(int newPriority) 更改线程的优先级。

  3. 联合
    Thread: public void join() throws InterruptedException
    一个线程A可以在运行时调用线程B的join()方法,这时A会停下,等待B线程先完成,再继续运行。
    join方法可以被中断,但是注意调用的是A.interrupted,此时A会重新获取运行权,B不再先运行。
    此时dopey线程在使用sleeper.join时,使用dopey.interrupt打断了join,dopey重新获取运行权

  4. 让步
    Thread: public static void yield() 建议其他线程可以运行,仅仅是建议,可能还是自己继续执行。不能依赖于yield()方法

  5. 后台线程
    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。

你可能感兴趣的:(线程-1. 线程基础)