美文网首页
线程-1. 线程基础

线程-1. 线程基础

作者: 棱锋 | 来源:发表于2018-08-02 23:26 被阅读0次

    前言:

    本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() 试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表。
    <T>Future<T> submit(Callable<T> 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> {
        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<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());
    }
    
    1. 使用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. 一些简单概念

    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. 线程基础

          本文链接:https://www.haomeiwen.com/subject/xyqdvftx.html