• 第二章、Java多线程入门类和接口


    一、Thread类和Runnable接口

      首先,我们需要有一个“线程”类。JDK提供了Thread类和Runnable接口来让我们实现自己的“线程”类

      • 继承Thread类,并重写run方法;

      • 实现Runnable接口的run方法;

    1.1、继承Thread类

      首先是继承Thread

    public class Ex_Thread extends Thread{
    
        @Override
        public void run() {
            System.out.println("Ex_Thread = " );
        }
    
        public static void main(String[] args) {
            Ex_Thread ex_thread = new Ex_Thread();
            ex_thread.start();
        }
    }

      注意要调用start()方法后,该线程才算启动!

      我们在程序里面调用了start()方法后,虚拟机会先为我们创建一个线程,然后等到这个线程第一次得到时间片时再调用run()方法注意不可多次调用start()方法。在第一次调用start()方法后,再次调用start()方法会抛出异常。

    1.2、实现Runnable接口

      接着我们来看一下Runnable接口(JDK 1.8 +):

    @FunctionalInterface
    public interface Runnable {
        public abstract void run();
    }

      可以看到Runnable是一个函数式接口,这意味着我们可以使用Java 8的函数式编程来简化代码。

      示例代码:

    public class Impl_Runnable implements Runnable {
        @Override
        public void run() {
            System.out.println("Impl_Runnable");
        }
    
        public static void main(String[] args) {
            new Thread(new Impl_Runnable()).start();
    
            new Thread(()->{
                System.out.println("Java 8 匿名内部类");
            }).start();
        }
    }

    1.3、Thread类构造方法

    • Runnable : 指定要执行的任务;
    • ThreadGroup : 线程组,指定这个线程是在哪个线程组下
    • String: 线程的名字,多个线程的名字是可以重复的。如果不指定名字
    //我们大多是直接调用下面两个构造方法:
    Thread(Runnable target)
    Thread(Runnable target, String name)

    1.4、Thread类的几个常用方法

      这里介绍一下Thread类的几个常用的方法:currentThread():静态方法,返回对当前正在执行的线程对象的引用;

      • start()开始执行线程的方法,java虚拟机会调用线程内的run()方法;

      • yield():yield在英语里有放弃的意思,同样,这里的yield()指的是当前线程愿意让出对当前处理器的占用。这里需要注意的是,就算当前线程调用了yield()方法,程序在调度的时候,也还有可能继续运行这个线程的;

        Java线程中的Thread.yield( )方法,译为线程让步。顾名思义,就是说当一个线程使用了这个方法之后,它就会把自己CPU执行的时间让掉,让自己或者其它的线程运行,注意是让自己或者其他线程运行,并不是单纯的让给其他线程。
        yield()的作用是让步。它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保
        证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行!
        
        举个例子:一帮朋友在排队上公交车,轮到Yield的时候,他突然说:我不想先上去了,咱们大家来竞赛上公交车。然后所有人就一块冲向公交车,有可能是其他人先上车了,也有可能是Yield先上车了。
        但是线程是有优先级的,优先级越高的人,就一定能第一个上车吗?这是不一定的,优先级高的人仅仅只是第一个上车的概率大了一点而已,
        最终第一个上车的,也有可能是优先级最低的人。并且所谓的优先级执行,是在大量执行次数中才能体现出来的。
      • sleep():静态方法,使当前线程睡眠一段时间;

      • join():使当前线程等待另一个线程执行完毕之后再继续执行,内部调用的是Object类的wait()方法实现的;
        从join方法的源码来看,join方法的本质调用的是Object中的wait方法实现线程的阻塞。调用wait方法必须要获取锁,所以join方法是被synchronized修饰的,synchronized修饰在方法层面相当于synchronized(this),this就是Thread本身的实例。join阻塞的是主线程

      • wait()
        • wait() 是针对已经获取对象锁的线程进行操作。
        • 当线程获取对象锁后,调用 wait() 主动释放对象锁,同时该线程休眠
        • 直到其他线程调用 notify() 唤醒该线程,才继续获取对象锁,并执行
        • 调用 notify() 唤醒线程并不是实时的,而是等相应的 synchronized 语句块执行结束,自动释放对象锁
        • 再由 JVM 选取休眠的线程赋予对象锁,并执行,从而实现线程间同步、唤醒的

    1.5、Thread类与Runnable接口的比较:

      实现一个自定义的线程类,可以有继承Thread类或者实现Runnable接口这两种方式,它们之间有什么优劣呢?

      • 由于Java“单继承,多实现”的特性,Runnable接口使用起来比Thread更灵活。

      • Runnable接口出现更符合面向对象,将线程单独进行对象的封装。

      • Runnable接口出现,降低了线程对象和线程任务的耦合性。

      • 如果使用线程时不需要使用Thread类的诸多方法,显然使用Runnable接口更为轻量。

      所以,我们通常优先使用“实现Runnable接口”这种方式来自定义线程类。

    二、Callable、Future与FutureTask

      通常来说,我们使用RunnableThread来创建一个新的线程。但是它们有一个弊端,就是run方法是没有返回值的。而有时候我们希望开启一个线程去执行一个任务,并且这个任务执行完成后有一个返回值。

      JDK提供了Callable接口与Future接口完成后有一个返回值为我们解决这个问题,这也是所谓的“异步”模型。

    2.1、Callable接口

      CallableRunnable类似,同样是只有一个抽象方法的函数式接口。不同的是,Callable提供的方法是有返回值的,而且支持泛型

    @FunctionalInterface
    public interface Callable<V> {
        V call() throws Exception;
    }

      那一般是怎么使用Callable的呢?Callable一般是配合线程池工具ExecutorService来使用的ExecutorService可以使用submit方法来让一个Callable接口执行。它会返回一个Future,我们后续的程序可以通过这个Futureget方法得到结果。

    public class Impl_Callable implements Callable<String> {
        @Override
        public String call() throws Exception {
            // 模拟计算需要一秒
            Thread.sleep(1000);
            return "Impl_Callable";
        }
    
        public static void main(String[] args) throws Exception{
     
            ExecutorService executor= Executors.newCachedThreadPool();
            Impl_Callable impl_callable = new Impl_Callable();
            // 注意调用get方法会阻塞当前线程,直到得到结果。
            // 所以实际编码中建议使用可以设置超时时间的重载get方法。
            Future<String> result = executor.submit(impl_callable);
            System.out.println(result.get());
        }
    }

    2.2、Future接口

      Future接口只有几个比较简单的方法:

    public abstract interface Future<V> {
        public abstract boolean cancel(boolean paramBoolean);
        public abstract boolean isCancelled();
        public abstract boolean isDone();
        public abstract V get() throws InterruptedException, ExecutionException;
        public abstract V get(long paramLong, TimeUnit paramTimeUnit)
                throws InterruptedException, ExecutionException, TimeoutException;
    }

      cancel法是试图取消一个线程的执行。

      注意是试图取消,并不一定能取消成功。因为任务可能已完成、已取消、或者一些其它因素不能取消,存在取消失败的可能。boolean类型的返回值是“是否取消成功”的意思。参数paramBoolean表示是否采用中断的方式取消线程执行。

      所以有时候,为了让任务有能够取消的功能,就使用Callable来代替Runnable。如果为了可取消性而使用 Future但又不提供可用的结果,则可以声明 Future形式类型、并返回 null作为底层任务的结果。

    2.3、FutureTask类

      上面介绍了Future接口。这个接口有一个实现类叫FutureTaskFutureTask是实现的RunnableFuture接口的,而RunnableFuture接口同时继承了Runnable接口和Future接口:

    public interface RunnableFuture<V> extends Runnable, Future<V> {
        void run();
    }

      那FutureTask类有什么用?为什么要有一个FutureTask类?前面说到了Future只是一个接口,而它里面的cancelgetisDone等方法要自己实现起来都是非常复杂的。所以JDK提供了一个FutureTask类来供我们使用。

    public class FutrueTask_Demo implements Callable<String> {
        @Override
        public String call() throws Exception {
            Thread.sleep(1000);
            return "FutrueTask_Demo";
        }
    
        public static void main(String[] args) throws Exception {
    
            ExecutorService executor = Executors.newCachedThreadPool();
            FutureTask<String> task = new FutureTask<>(new FutrueTask_Demo() );
            executor.submit(task);
            System.out.println(task.get());
        }
    }

      使用上与第一个Demo有一点小的区别。首先,调用submit方法是没有返回值的。这里实际上是调用的submit(Runnable task)方法,而上面的Demo,调用的是submit(Callable task)方法。

      然后,这里是使用FutureTask直接取get取值,而上面的Demo是通过submit方法返回的Future去取值。

      在很多高并发的环境下,有可能Callable和FutureTask会创建多次。FutureTask能够在高并发环境下确保任务只执行一次。这块有兴趣的同学可以参看FutureTask源码。

    2.4、FutureTask的几个状态

    /**
      *
      * state可能的状态转变路径如下:
      * NEW -> COMPLETING -> NORMAL
      * NEW -> COMPLETING -> EXCEPTIONAL
      * NEW -> CANCELLED
      * NEW -> INTERRUPTING -> INTERRUPTED
      */
    private volatile int state;
    private static final int NEW          = 0;
    private static final int COMPLETING   = 1;
    private static final int NORMAL       = 2;
    private static final int EXCEPTIONAL  = 3;
    private static final int CANCELLED    = 4;
    private static final int INTERRUPTING = 5;
    private static final int INTERRUPTED  = 6;

      state表示任务的运行状态,初始状态为NEW。运行状态只会在set、setException、cancel方法中终止。COMPLETING、INTERRUPTING是任务完成后的瞬时状态。

      以上就是Java多线程几个基本的类和接口的介绍。可以打开JDK看看源码,体会这几个类的设计思路和用途吧!

  • 相关阅读:
    Django(十五)模板详解:模板标签、过滤器、模板注释、模板继承、html转义
    Python @函数装饰器及用法
    NPM概述及使用简介
    MVC、MVT简介
    十、Vue:Vuex实现data(){}内数据多个组件间共享
    九、响应式发:rem和less(适配移动端)
    八、Vue-lazyload
    Vue点到子路由,父级,无法高亮问题解决
    七、Vue组件库:Element、Swiper(轮播专用组件)
    六、Vue-Router:基础路由处理、路由提取成单独文件、路由嵌套、路由传参数、路由高亮、html5的history使用
  • 原文地址:https://www.cnblogs.com/jdy1022/p/13902318.html
Copyright © 2020-2023  润新知