精华内容
下载资源
问答
  • 主要介绍了Java向Runnable线程传递参数方法实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
  • 主要介绍了详解Java中多线程异常捕获Runnable的实现的相关资料,希望通过本文能帮助到大家,让大家理解掌握这样的知识,需要的朋友可以参考下
  • 主要为大家详细介绍了java实现Runnable接口适合资源的共享,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • Runnable

    万次阅读 2018-01-18 20:17:59
    Runnable 概述 java version “1.8.0_131” 多线程创建方式之一 public class RunnableTest implements Runnable{ // 实现 run() public void run() { System.out.println("thread"); } publ

    Runnable


    概述

    • java version “1.8.0_131”
    • 多线程创建方式之一
    public class RunnableTest implements Runnable{
    
        // 实现 run() 
        public void run() {
            System.out.println("thread");
        }
    
        public static void main(String[] args) {
            RunnableTest test = new RunnableTest();
            // 线程启动由Thread负责
            Thread thread = new Thread(test);
            thread.start();
        }
    }
    • Runnable只有一个抽象的run()方法,此方法是在运行时由JVM调用,每一个运行期的Runnable实现类实例(包括Thread的子类,因为Thread亦是实现了Runnable接口)都对应于操作系统中的一个线程,所以说Java中的线程只是操作系统线程的一个映射,Java中线程的运行效率也不可能高于底层语言线程,因为Java中线程的创建和调用需要经过JVM,JVM再向下调用(JNI的方式与特定平台进行通信)

    类注释

    /**
     * The <code>Runnable</code> interface should be implemented by any
     * class whose instances are intended to be executed by a thread. The
     * class must define a method of no arguments called <code>run</code>.
     * <p>
     * This interface is designed to provide a common protocol for objects that
     * wish to execute code while they are active. For example,
     * <code>Runnable</code> is implemented by class <code>Thread</code>.
     * Being active simply means that a thread has been started and has not
     * yet been stopped.
     * <p>
     * In addition, <code>Runnable</code> provides the means for a class to be
     * active while not subclassing <code>Thread</code>. A class that implements
     * <code>Runnable</code> can run without subclassing <code>Thread</code>
     * by instantiating a <code>Thread</code> instance and passing itself in
     * as the target.  In most cases, the <code>Runnable</code> interface should
     * be used if you are only planning to override the <code>run()</code>
     * method and no other <code>Thread</code> methods.
     * This is important because classes should not be subclassed
     * unless the programmer intends on modifying or enhancing the fundamental
     * behavior of the class.
     *  * @author  Arthur van Hoff
     * @see     java.lang.Thread
     * @see     java.util.concurrent.Callable
     * @since   JDK1.0
     */
    • Runnable 接口可以被任何想要被一个线程运行的接口继承实现;继承 Runnable 接口的类必须有一个 run() 方法;
    • Runnable 接口被设计的目的是为那些当其处于活跃期时期望运行代码的对象提供一个公共的协议;处于活跃期简单的说就是一个线程已经启动但还没有结束
    • 继承 Runnable 接口实现线程,不需继承 Thread;而将类本身作为 Thread 中的目标 target
    • Runnable 接口最好不要继承,除非开发者想要更好的扩展此接口的功能

    类注解

    @FunctionalInterface
    public interface Runnable {
    }
    • @FunctionalInterface 用来标记函数式编程接口
      • 函数式编程接口:指仅仅只包含一个抽象方法的接口
      • 该注解不是必须的,如果一个接口符合”函数式接口”定义,那么加不加该注解都没有影响
      • 加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错

    run() 方法

        /**
         * When an object implementing interface <code>Runnable</code> is used
         * to create a thread, starting the thread causes the object's
         * <code>run</code> method to be called in that separately executing
         * thread.
         * <p>
         * The general contract of the method <code>run</code> is that it may
         * take any action whatsoever.
         *
         * @see     java.lang.Thread#run()
         */
        public abstract void run();
    • 当一个对象实现 Runnnable 接口被用来创建一个线程,线程的启动导致该对象的 run 方法被另一个线程调用

    参考资料

    展开全文
  • java中实现多线程有两种方法:一种是继承Thread类,另一种是实现Runnable接口。
  • 本课讲的是如何实现一个Runnable,在一个独立线程上运行Runnable.run()方法.Runnable对象执行特别操作有时叫作任务。 Thread和Runnable都是基础的类,靠他们自己,能力有限。作为替代,Android有强大的基础类,像...
  • android demo,Runnable异步线程的处理
  • 下面小编就为大家带来一篇java实现多线程的两种方式继承Thread类和实现Runnable接口的方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • 主要为大家详细介绍了Java多线程如何实现Runnable方式,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • android demo,使用Handler的postDelay,Runnable run实现延时3秒的splash。
  • 主要为大家详细介绍了如何使用Runnable实现数据共享,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • Thread和Runnable 的区别在面试当中是比较容易考的,自己又刚好在做这个的实验课题,于是乎,决定好好的学习这一部分知识,并把我查过的资料,通过自己的理解,给大家整理出来。  目录  面向小白学习法——Thread...
  • 1、Java有两种实现多线程的方式:通过Runnable接口、通过Thread直接实现,请掌握这两种实现方式,并编写示例程序。 2、多线程是并发执行的,交替占有cpu执行,请编写示例程序,并观察输出结果。 3、采用线程同步方法...
  • Runnable是一个一站式的代码片段集合网站,你不仅可以搜索代码,还可以编辑、运行这些代码片段,以确保它们的正确性与可用性
  • 本篇文章是对在Android中Handler Runnable与Thread的区别进行了详细的分析介绍,需要的朋友参考下
  • 主要介绍了Java中继承thread类与实现Runnable接口的比较的相关资料,需要的朋友可以参考下
  • 本篇文章介绍了,java中使用Runnable接口创建线程的方法。需要的朋友参考下
  • 一个简单的多线程代码示例,Java实现,用于实现同一时刻,只允许一个线程调用执行的代码块或类,即synchronized的如何使用(多线程实现),实现 Runnable
  • 主要介绍了Java中的Runnable,Callable,Future,FutureTask的比较的相关资料,需要的朋友可以参考下
  • java多线程Runnable实例

    2014-01-24 21:38:58
    java多线程runnable实例,经过测试的,可以直接运行
  • android handler runnable使用实例(关键是内部run中停止)
  • Java线程状态RUNNABLE详解

    千次阅读 2020-11-03 20:53:50
    什么是 RUNNABLE? 直接看它的 Javadoc 中的说明: 一个在 JVM 中执行 的线程处于这一状态中。(A thread executing in the Java virtual machine is in this state.) 而传统的进(线)程状态一般划分如下(基于...

    Java虚拟机层面所暴露给我们的状态,与操作系统底层的线程状态是两个不同层面的事。具体而言,这里说的 Java 线程状态均来自于 Thread 类下的 State 这一内部枚举类中所定义的状态:

    在这里插入图片描述

    什么是 RUNNABLE?

    直接看它的 Javadoc 中的说明:

    一个在 JVM 中执行 的线程处于这一状态中。(A thread executing in the Java virtual machine is in this state.)

    而传统的进(线)程状态一般划分如下(基于操作系统层面,以cpu为中心):

    在这里插入图片描述

    注:这里的进程指早期的单线程 进程,这里所谓进程状态实质就是线程状态。

    那么 runnable 与图中的 ready 与 running 区别在哪呢?

    与传统的ready状态的区别

    更具体点,javadoc 中是这样说的:

    处于 runnable 状态下的线程正在 Java 虚拟机中执行,但它可能正在等待 来自于操作系统的其它资源,比如处理器。A thread in the runnable state is executing in the Java virtual machine but it may be waiting for other resources from the operating system such as processor.

    显然,runnable 状态实质上是包括了 ready 状态的。甚至还可能有包括上图中的 waiting 状态的部分细分状态,在后面我们将会看到这一点。

    与传统的running状态的区别

    有人常觉得 Java 线程状态中还少了个 running 状态,这其实是把两个不同层面的状态混淆了。对 Java 线程状态而言,不存在所谓的running 状态,它的 runnable 状态包含了 running 状态。

    我们可能会问,为何 JVM 中没有去区分这两种状态呢?

    现在的时分 (time-sharing)多任务 (multi-task)操作系统架构通常都是用所谓的“时间分片 (time quantum or time slice)”方式进行抢占式 (preemptive)轮转调度(round-robin式)。更复杂的可能还会加入优先级(priority)的机制。

    这个时间分片通常是很小的,一个线程一次最多只能在 cpu 上运行比如10-20ms 的时间(此时处于 running 状态),也即大概只有0.01秒这一量级,时间片用后就要被切换下来放入调度队列的末尾等待再次调度。(也即回到 ready 状态)

    注:如果期间进行了 I/O 的操作还会导致提前释放时间分片,并进入等待队列。 又或者是时间分片没有用完就被抢占,这时也是回到 ready 状态。

    这一切换的过程称为线程的上下文切换 (context switch),当然 cpu 不是简单地把线程踢开就完了,还需要把被相应的执行状态保存到内存中以便后续的恢复执行。

    显然,10-20ms 对人而言是很快的, 不计切换开销(每次在1ms 以内),相当于1秒内有50-100次切换。事实上时间片经常没用完,线程就因为各种原因被中断,实际发生的切换次数还会更多。

    也这正是单核 CPU 上实现所谓的“ 并发(concurrent)”的基本原理,但其实是快速切换所带来的假象,这有点类似一个手脚非常快的杂耍演员可以让好多个球同时在空中运转那般。

    时间分片也是可配置的,如果不追求在多个线程间很快的响应,也可以把这个时间配置得大一点,以减少切换带来的开销。

    如果是多核CPU,才有可能实现真正意义上的并发,这种情况通常也叫并行 ,不过你可能也会看到这两词会被混着用,这里就不去纠结它们的区别了。

    通常,Java的线程状态是服务于监控的,如果线程切换得是如此之快,那么区分 ready 与 running 就没什么太大意义了。

    当你看到监控上显示是 running 时,对应的线程可能早就被切换下去了,甚至又再次地切换了上来,也许你只能看到 ready 与 running 两个状态在快速地闪烁。

    当然,对于精确的性能评估而言,获得准确的 running 时间是有必要的。

    现今主流的 JVM 实现都把 Java 线程一一映射到操作系统底层的线程上,把调度委托给了操作系统,我们在虚拟机层面看到的状态实质是对底层状态的映射及包装(非一一对应关系)。JVM 本身没有做什么实质的调度,把底层的 ready 及 running 状态映射上来也没多大意义,因此,统一成为runnable 状态是不错的选择。

    我们将看到,Java 线程状态的改变通常只与自身显式引入的机制有关。

    当I/O阻塞时

    我们知道传统的I/O都是阻塞式(blocked)的,原因是I/O操作比起cpu来实在是太慢了,可能差到好几个数量级都说不定。如果让 cpu 去等I/O 的操作,很可能时间片都用完了,I/O 操作还没完成呢,不管怎样,它会导致 cpu 的利用率极低。

    所以,解决办法就是:一旦线程中执行到 I/O 有关的代码,相应线程立马被切走,然后调度 ready 队列中另一个线程来运行。这时执行了 I/O 的线程就不再运行,即所谓的被阻塞了。它也不会被放到调度队列中去,因为很可能再次调度到它时,I/O 可能仍没有完成。

    线程会被放到所谓的等待队列中,处于上图中的 waiting 状态:

    在这里插入图片描述
    当然了,我们所谓阻塞只是指这段时间 cpu 暂时不会理它了,但另一个部件比如硬盘则在努力地为它服务。cpu 与硬盘间是并行的。如果把线程视作为一个 job,这一 job 由 cpu 与硬盘交替协作完成,当在 cpu 上是 waiting 时,在硬盘上却处于 running,只是我们在操作系统层面讨论线程状态时通常是围绕着 cpu 这一中心去述说的。

    而当 I/O 完成时,则用一种叫中断 (interrupt)的机制来通知 cpu:也即所谓的“中断驱动 (interrupt-driven)”,现代操作系统基本都采用这一机制。

    某种意义上,这也是控制反转 (IoC)机制的一种体现,cpu不用反复去询问硬盘,这也是所谓的“好莱坞原则”—Don’t call us, we will call you.好莱坞的经纪人经常对演员们说:“别打电话给我,(有戏时)我们会打电话给你。”

    在这里,硬盘与 cpu 的互动机制也是类似,硬盘对 cpu 说:”别老来问我 IO 做完了没有,完了我自然会通知你的“

    当然了,cpu 还是要不断地检查中断,就好比演员们也要时刻注意接听电话,不过这总好过不断主动去询问,毕竟绝大多数的询问都将是徒劳的。

    cpu 会收到一个比如说来自硬盘的中断信号,并进入中断处理例程,手头正在执行的线程因此被打断,回到 ready 队列。而先前因 I/O 而waiting 的线程随着 I/O 的完成也再次回到 ready 队列,这时 cpu 可能会选择它来执行。

    另一方面,所谓的时间分片轮转本质上也是由一个定时器定时中断来驱动的,可以使线程从 running 回到 ready 状态:

    在这里插入图片描述
    比如设置一个10ms 的倒计时,时间一到就发一个中断,好像大限已到一样,然后重置倒计时,如此循环。与 cpu 正打得火热的线程可能不情愿听到这一中断信号,因为它意味着这一次与 cpu 缠绵的时间又要到头了…奴为出来难,何日君再来?

    现在我们再看一下 Java 中定义的线程状态,嘿,它也有 BLOCKED(阻塞),也有 WAITING(等待),甚至它还更细,还有TIMED_WAITING。

    现在问题来了,进行阻塞式 I/O 操作时(相对于底层系统cpu来说,线程为阻塞状态),Java 的线程状态(jvm层面上)究竟是什么?是 BLOCKED?还是 WAITING?

    可能你已经猜到,既然放到 RUNNABLE 这一主题下讨论,其实状态还是 RUNNABLE。我们也可以通过一些测试来验证这一点:

    @Test
    public void testInBlockedIOState() throws InterruptedException {
        Scanner in = new Scanner(System.in);
        // 创建一个名为“输入输出”的线程t
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 命令行中的阻塞读
                    String input = in.nextLine();
                    System.out.println(input);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                  IOUtils.closeQuietly(in);
                }
            }
        }, "输入输出"); // 线程的名字
    
        // 启动
        t.start();
    
        // 确保run已经得到执行
        Thread.sleep(100);
    
        // 状态为RUNNABLE
        assertThat(t.getState()).isEqualTo(Thread.State.RUNNABLE);
    }
    

    在最后的语句上加一断点,监控上也反映了这一点:

    在这里插入图片描述
    网络阻塞时同理,比如socket.accept,我们说这是一个“阻塞式(blocked)”式方法,但线程状态还是 RUNNABLE。

    @Test
    public void testBlockedSocketState() throws Exception {
        Thread serverThread = new Thread(new Runnable() {
            @Override
            public void run() {
                ServerSocket serverSocket = null;
                try {
                    serverSocket = new ServerSocket(10086);
                    while (true) {
                        // 阻塞的accept方法
                        Socket socket = serverSocket.accept();
                        // TODO
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    try {
                        serverSocket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "socket线程"); // 线程的名字
        serverThread.start();
    
        // 确保run已经得到执行
        Thread.sleep(500);
    
        // 状态为RUNNABLE
        assertThat(serverThread.getState()).isEqualTo(Thread.State.RUNNABLE);
    
    }
    

    监控显示:

    在这里插入图片描述
    当然,Java 很早就引入了所谓 nio(新的IO)包,至于用 nio 时线程状态究竟是怎样的,这里就不再一一具体去分析了。至少我们看到了,进行传统上的 IO 操作时,口语上我们也会说“阻塞”,但这个“阻塞”与线程的 BLOCKED 状态是两码事!

    如何看待RUNNABLE状态?

    首先还是前面说的,注意分清两个层面:虚拟机层面(vm)和操作系统层面(os)

    虚拟机是骑在你操作系统上面的,身下的操作系统是作为某种资源为满足虚拟机的需求而存在的。当进行阻塞式的 IO 操作时,或许底层的操作系统线程确实处在阻塞状态,但我们关心的是 JVM 的线程状态。

    JVM 并不关心底层的实现细节,什么时间分片也好,什么 IO 时就要切换也好,它并不关心。

    前面说到,“处于 runnable 状态下的线程正在* Java 虚拟机中执行,但它 可能正在等待*来自于操作系统的其它资源,比如处理器。”

    JVM 把那些都视作资源,cpu 也好,硬盘,网卡也罢,有东西在为线程服务,它就认为线程在“执行”。处于 IO 阻塞,只是说 cpu 不执行线程了,但网卡可能还在监听呀,虽然可能暂时没有收到数据。

    所以 JVM 认为线程还在执行。而操作系统的线程状态是围绕着 cpu 这一核心去述说的,这与 JVM 的侧重点是有所不同的。

    前面我们也强调了“Java 线程状态的改变通常只与自身显式引入的机制有关”,如果 JVM 中的线程状态发生改变了,通常是自身机制引发的。比如 synchronize 机制有可能让线程进入BLOCKED 状态,sleep,wait等方法则可能让其进入 WATING 之类的状态。

    展开全文
  • 在Java中创建线程有两种方法:使用Thread类和使用Runnable接口。在使用Runnable接口时需要建立一个Thread实例。因此,无论是通过Thread类还是Runnable接口建立线程,都必须建立Thread类或它的子类的实例。
  • Thread类、Runnable接口详解

    千次阅读 2019-12-26 23:08:02
    前言 Thread类想必都不陌生,第一次学习多线程的时候就一定会接触Thread类。...答案是两种:继承Thread类、实现Runnable接口。 说只有两种,有人可能就不服了,实现Callable接口为什么不算?线程池为...

    前言

    Thread类想必都不陌生,第一次学习多线程的时候就一定会接触Thread类。本篇主要从Thread类的定义、使用、注意事项、源码等方面入手,全方位的讲解Thread类。

    Thread

    我们经常会被问到这样一个问题:

    Java开启一个新线程有哪几种方法?

    答案是两种:继承Thread类、实现Runnable接口。

    说只有两种,有人可能就不服了,实现Callable接口为什么不算?线程池为什么不算?
    Oracle官方说明如下:
    https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html
    其中已经写得很明白

    There are two ways to create a new thread of execution.
    One is to declare a class to be a subclass of Thread.This subclass should override the run method of class Thread.
    The other way to create a thread is to declare a class that implements the Runnable interface. That class then implements the run method.

    有两种方式创建一个新的执行线程
    一种是定义Thread的子类,子类重写run方法
    另一种是定义Runnable接口的实现类,实现run方法

    至于为什么实现Callable接口和线程池不算,以后的博客会详细介绍。

    一个小问题相信已经让大家回忆起了Thread类相关的知识,接下来就从源码的角度解析Thread

    定义

    Thread类从JDK1.0版本开始就有了,可谓是历史悠久。本篇以JDK1.8为例进行源码讲解,Thread类定义如下(只列出需要重点关注的成员变量、常量):

    // Thread类实现了Runnable接口
    public class Thread implements Runnable {
    
    	// 是否是守护线程
        private boolean daemon = false;
        
       // 最小优先级
        public final static int MIN_PRIORITY = 1;
    
       // 默认优先级
        public final static int NORM_PRIORITY = 5;
    
        // 最大优先级
        public final static int MAX_PRIORITY = 10;
    
    	// 线程名称
        private volatile char  name[];
        // 线程优先级
        private int priority;
    
    	// 需要执行的单元
        private Runnable target;
    
    	// 线程状态
    	private volatile int threadStatus = 0;
    
    	// 线程ID
    	private long tid;
    }
    

    Thread类的定义可以看出,对于一个Thread,需要重点关注的有以下几点:

    • 实现了Runnable接口
    • 线程需要重点关注的四个属性:ID、Name、是否是守护线程、优先级
    • 线程的状态需要特别注意

    接下来就从这三点分别进行详细讲解,因为线程的状态之前已经专门写过一篇博客:Java线程到底有几种状态。所以重点讲解其余的两点

    实现Runnable接口

    前文说到Java开启一个新线程的两种方式:继承Thread类,重写run方法;实现Runnable接口,实现run方法。接下来就来看一下Thread类中的run方法:

    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
    

    其中targetRunnable类型的引用,也可以看做线程的执行单元,结合下面一个小实例:

    /**
     * @author sicimike
     */
    public class CreateThreadDemo {
    
        public static void main(String[] args) {
            new SicThread1().start();
            new Thread(new SicThread2()).start();
        }
    
    }
    
    class SicThread1 extends Thread {
        @Override
        public void run() {
            System.out.println("extends Thread");
        }
    }
    
    class SicThread2 implements Runnable {
        @Override
        public void run() {
            System.out.println("implements Runnable");
        }
    }
    

    在代码

    new SicThread1().start();
    

    中调用的SicThread1start方法,间接调用重写的run方法。

    SicThread2直接实现了Runnable接口,在代码

    new Thread(new SicThread2()).start();
    

    中调用的是构造方法public Thread(Runnable target) {...}
    由于Thread类实现了Runnable接口,相当于SicThread1也实现了Runnable接口,所以也可以写new Thread(new SicThread1()).start();这样的代码来启动线程。

    也就是说,不管是继承Thread类还是实现Runnable接口,都是利用Thread类的run方法。只是前者是重写了Thread类的run方法,后者是给Thread类传递一个Runnable target,调用targetrun方法。至于这两种方法本质上算不算同一种,这就“仁者见仁,智者见智”了。既然Oracle认为是两种,那还是以官方描述为准。

    那这两种方式,哪一种更好
    毫无疑问,实现Runnable接口更好,理由有三:

    • 解耦角度:Runnable接口只定义了一个抽象方法run,语义非常明确,就是线程需要执行的任务。而Thread类除了线程需要执行的任务,还需要维护线程的生命周期、状态转换等
    • 资源角度:继承Thread类的方式,如果想要执行一个任务,必须新建一个线程,执行完成后还要销毁,开销非常大;而实现Runnable接口只需要新建任务,可以做到同一个线程执行多个任务,大大减小了线程创建、销毁的资源浪费
    • 扩展角度:Java不支持多继承,一个类如果继承了Thread类就不能再继承别的类,不利于未来的扩展

    四个属性

    属性用途/说明
    ID(Long)唯一标识不同的线程
    Name(char[])线程名称,用于调试 、定位问题等
    daemon(boolean)是否是守护线程,true表示是守护线程,false表示非守护线程(用户线程)
    priority(int)用于告诉CPU哪些线程希望被更多的执行,哪些线程希望被更少的执行

    线程ID

    线程ID从1(主线程)开始自增,(程序)不能手动修改。现在看下Thread类中关于ID的部分:

    // 线程ID
    private long tid;
    
    public long getId() {
        return tid;
    }
    
    // jdk1.8.0_101版本,第422行
    // 设置线程ID
    /* Set thread ID */
    tid = nextThreadID();
    
    // 用于生成线程ID
    private static long threadSeqNumber;
    
    // 加锁的自增操作
    private static synchronized long nextThreadID() {
        return ++threadSeqNumber;
    }
    

    从源码可以看出ID的两个特点:

    • 从1开始自增
    • 不能手动修改

    线程Name

    看了线程ID相关的源码后,很容易就总结除了线程ID相关的特点。所以同样看下Thread类关于Name的重要操作:

    // 不传入名字时,默认就是"Thread-" + 数字
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
    
    public Thread(Runnable target, String name) {
    init(null, target, name, 0);
    }
    
    // 用于匿名线程编号
    private static int threadInitNumber;
    
    // 加锁的自增操作
    private static synchronized int nextThreadNum() {
        return threadInitNumber++;
    }
    
    // 可以动态设置线程name
    public final synchronized void setName(String name) {
    	// 确定当前线程有修改该线程的权限
        checkAccess();
        // 设置线程名字
        this.name = name.toCharArray();
        if (threadStatus != 0) {
        	// 如果线程不是处于0(线程未启动)状态,则不能修改native层的name
            setNativeName(name);
        }
    }
    
    private native void setNativeName(String name);
    

    至此,可以总结出关于线程Name的两个特点:

    • 默认线程名称是"Thread-" + 数字(从0开始),为了方便调试,应该给每个线程取一个有意义的名字
    • 实例化时如果没有设置线程Name,之后还可以通过setName的方式设置线程Name

    守护线程

    守护线程的主要作用是为了给用户线程提供一系列服务,守护线程有三个特点:

    • 线程类型默认继承自父线程:守护线程创建的线程默认就是守护线程;用户线程创建的线程默认就是用户线程,可以通过setDaemon方法修改这个属性
    • 守护线程一般由JVM启动
    • 守护线程不影响JVM的退出

    守护线程和用户线程本质上没有多大区别,最大的区别就是守护线程不影响JVM的退出。

    线程优先级

    Java中定义的线程优先级有1-10(十个等级,数值越大,优先级越高),默认为5。虽然Thread类定义优先级这个功能,但是程序的设计不应该依赖于优先级。究其原因,主要有两点:

    • Thread类中定义的优先级不代表操作系统的优先级,不同的操作系统有不同的优先级定义
    • 优先级可能被操作系统改变

    核心方法

    了解了Thread的定义及核心属性后,再来看看Thread的核心方法startsleepjoinyield

    start方法

    启动一个线程的方式就是调用它的start()方法,而不是run()方法。有时也会被问到这样两个问题:

    同一个线程两次(多次)调用start方法会怎样?
    启动一个线程为什么不能调用run方法,而是start方法?

    看完start方法的实现,能轻松回答这两个问题,下面是start方法的实现

    public synchronized void start() {
    
       if (threadStatus != 0)
       		// 如果线程状态不是“未启动”,会抛出IllegalThreadStateException异常
       		// 这里就回答了上面的第一个问题
           throw new IllegalThreadStateException();
    
       // 加入线程组
       group.add(this);
    
    	// 线程是否已经启动,启动后设置成true
       boolean started = false;
       try {
           start0();
           started = true;
       } finally {
           try {
               if (!started) {
               	   // 启动失败,把线程从线程组中删除
                   group.threadStartFailed(this);
               }
           } catch (Throwable ignore) {
               /* do nothing. If start0 threw a Throwable then
                 it will be passed up the call stack */
           }
       }
    }
    
    // 真正的启动线程的方法(native方法)
    private native void start0();
    

    根据start方法的实现可以总结出start做了哪些逻辑:

    • 检查线程状态
    • 加入线程组
    • 调用native方法start0通知JVM启动一个新线程
    • 如果启动失败,从线程组中删除线程

    再来回顾下Threadrun方法的实现:

    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
    

    对比这两个方法就可看出,start方法为线程的启动做了一系列准备,再去通知JVM启动一个新线程;而run方法仅仅是一个普通方法,所以不能启动一个新线程。

    sleep方法

    sleep(long millis)方法的作用是让线程休眠指定的时间,在指定时间内不占用CPU资源。sleep方法的特点有以下几点:

    • 线程处于TIMED_WAITING状态
    • sleep期间不占用CPU资源
    • sleep期间不释放锁(Synchronized锁和ReentrantLock都不释放)
    • sleep方法能响应中断,检测到中断后抛出InterruptedException然后清除中断状态

    join方法

    join的作用是阻塞当前线程等待加入的线程执行完成后再继续执行。使用这个方法一定要清楚是哪个线程被阻塞,举个例子:

    /**
     * 使用join方法
     * @author sicimike
     */
    public class ThreadJoinDemo {
    
        public static void main(String[] args) {
            Thread thread = new Thread(()->{
                // 可以在此适当的休眠,使结果更清晰
                System.out.println("sub thread");
            });
    
            thread.start();
            try {
                // join方法
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("main thread");
        }
    }
    

    执行结果

    sub thread
    main thread
    

    在主线程中调用thread.join(),结果子线程先输出,主线程后输出。这个结果是确定的,不存在随机性。这就是join方法的作用,主线程中调用子线程的join方法,阻塞的主线程,等待子线程执行完成后,主线程继续执行。
    日常编码中应该尽量避免使用join方法,而是使用JDK封装好的并发工具CountDownLatchCyclicBarrier代替:并发工具三巨头CountDownLatch、CyclicBarrier、Semaphore使用

    接下来看下join方法的实现:

    public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;
    
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
    
        if (millis == 0) {
        	// 未设置超时时间,一直被阻塞
            while (isAlive()) {
            	// 线程处于可用状态(既不是NEW,也不是TERMINATED)
            	// 永久阻塞
                wait(0);
            }
        } else {
            while (isAlive()) {
            	// 线程处于可用状态(既不是NEW,也不是TERMINATED)
            	// 计算剩余的阻塞时间
                long delay = millis - now;
                if (delay <= 0) {
                	// 阻塞时间已经到了
                    break;
                }
                // 阻塞时间未到,阻塞指定时间
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
    

    通过源码可以看出,Thread类中的阻塞是通过wait方法实现的。
    值得注意的是:整个方法执行结束也没有执行notify或者notifyAll方法。因为Thread类的run执行结束后,会自动执行notifyAll方法。这也是Thread类不适合作为锁对象的原因。

    join方法的特点有以下几点:

    • 线程处于WAITING或者TIMED_WAITING状态
    • 底层调用的wait方法
    • 能响应中断,检测到中断后抛出InterruptedException然后清除中断状态

    yield方法

    yield方法的作用是释放CPU时间片,然后重新竞争。该方法不会释放锁,也不会改变线程状态,线程始终处于RUNNABLE状态。

    停止线程

    如何停止线程是一个比较大的话题,之前特意单独拿出来写过: 如何优雅的中断线程 ,此处就不再赘述。

    总结

    本篇主要深入源码,结合实例较为完整的讲解了JDK中的线程Thread类。具体讲解的内容有线程的类定义、成员变量/常量、核心属性、核心方法、线程启动、线程终止等。

    以上便是本篇的全部内容。

    展开全文
  • 大家都知道Runnable和Callable接口都可以作为其他线程执行的任务,但是Runnable接口的run方法没有返回值,而Callable接口的call方法有返回值,那么Callable接口是如何做到的呢?在此我给出一个Demo,看看通过...
  • Runnable和Thread源码分析

    千次阅读 2019-01-04 21:35:51
    Runnable和Thread源码分析 问题的引入: Runnable是一个接口,它只有一个run()抽象方法,run()抽象方法需要被实现后才能使用。Thread类继承了Runnable接口,并且Thread中实现了run()方法。最后,通过Thread类的...

    Runnable和Thread源码分析

    问题的引入
    Runnable是一个接口,它只有一个run()抽象方法,run()抽象方法需要被实现后才能使用。Thread类继承了Runnable接口,并且Thread中实现了run()方法。最后,通过Thread类的start()方法就可以启动线程了。这是我们平时在应用中使用线程的常用方法。如果多加思考你会发现以下几个问题:
    1、为什么调用start()方法就可以启动线程,而不是调用run()。start()方法和决定线程运行内容的run()方法又有什么联系呢?
    2、在Runnable层面实现run()方法和Thread层面实现run()方法的区别。
    3、怎么用Runnable实现run方法,并在Thread中执行Runnable实例化后重写的run方法。
    不着急下结论,我们通过分析源码来理清两者的关系。
    Runnable源码
    打开如下Runnable的源码,我们可以发现,它只含有一个抽象的run()方法。因为它是接口,所以需要被继承后重写run()方法才能使用。我们可以简单思考一下,如果需要启动一个新的线程,这意味着需要分配给它CPU资源来执行这个线程,而CPU是不归JVM(Java虚拟机)直接管辖的,自然需要通过JVM通过外部的接口来实现和操作系统的对话,调整CPU资源的分配,所以,线程的创建一定是一个native方法(实现Java调用底层的C、C++代码)。接口中的run()方法中的内容,仅仅代表线程运行的内容。一个结论出来了,Thread类中必然有native方法,Runnable无法脱离Thread类来新建线程。如果我们在new一个线程对象后直接调用run方法,也只是让当前线程去执行run()方法中的语句罢了,并没有实现多线程。

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

    Thread源码
    接下来我们看看Thread类的源码。
    从下面这句我们可以看出Thread继承了Runnbale接口。

    public class Thread implements Runnable {
    

    下面是Thread类的run()方法。里面的target对象是什么?为什么target能调用run()方法?这个run()方法是谁的run()方法?

        @Override
        public void run() {
            if (target != null) {
                target.run();
            }
        }
    

    为了解决问题,我们把关注点放到Thread类的这几行代码上。在Thread类的一个构造方法中Runnable接口的实例化对象target被传入其中,通过几个方法的调用,并通过this.target = target;语句把Runnable的实例化对象保存在了Thread类中,成为了Thread类的一个成员变量,那么它调用的run()方法自然是在Runnable实例化中重写的run()方法。

        private Runnable target;
        public Thread(Runnable target) {
            init(null, target, "Thread-" + nextThreadNum(), 0);        
        }    
       private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
            init(g, target, name, stackSize, null);
        }
       private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc) {
              ....................  //省略
             this.target = target;
             ......................//省略
        }
    

    这里大家可能会有一点疑问,Runnable是一个接口,接口为什么会有对象?
    Runnable入口参数的两种实现形式
    1、定义一个类继承Runnable接口,并重写run()方法,创建这个类的实例化对象,在new一个Thread类对象的时候作为入口参数传入构造方法。根据里氏代换原则,任何基类(父类,在这里接口也适用)可以出现的地方,子类也可以出现(此处可以理解为向上转型),这个类的对象可以传入Thread类的构造方法。实现代码如下:

    class MyThread implements Runnable{
    	public void run() {	    
    		while(ticket>0)
    		{
    			System.out.println("");
    		}
    	}
    }
    

    2、通过匿名内部类,直接new一个Runnable然后紧接着在后面重写run方法,把它作为参数传入构造方法,因为没有对象名,叫做匿名内部类,它可以实例化接口和抽象类。类似如下实现:

              Thread th1 = new Thread(new Runnable()
             	{
      		         public void run()
      		         {
      		        	 System.out.println("");
      		         }
      			});
    

    start()方法
    最后再看看线程创建的核心,start()方法。这里我们不关注其他的变量和内容,只关注线程的实现。我们发现在这个方法中调用了start0()方法。

        public synchronized void start() {
            group.add(this);
            boolean started = false;
            try {
                start0();
                started = true;
            } finally {
                try {
                    if (!started) {
                        group.threadStartFailed(this);
                    }
                } catch (Throwable ignore) {
                    /* do nothing. If start0 threw a Throwable then
                      it will be passed up the call stack */
                }
            }
        }
    

    start0()方法就是我们一直在寻找的native方法,它没有Java的方法体,它关系到下层C、C++语言的调用,执行这个方法就会为线程申请物理资源并启动线程,线程运行后,便会执行run()方法。

        private native void start0();
        /**
         * If this thread was constructed using a separate
         * <code>Runnable</code> run object, then that
         * <code>Runnable</code> object's <code>run</code> method is called;
         * otherwise, this method does nothing and returns.
         * <p>
         * Subclasses of <code>Thread</code> should override this method.
         *
         * @see     #start()
         * @see     #stop()
         * @see     #Thread(ThreadGroup, Runnable, String)
         */
    

    思路整理
    关键:run()方法存在于Thread类和Runnable接口中。
    1、如果我们选择创建一个类继承Thread,然后重写run()方法,原先的run()方法被子类重写的run()方法覆盖,原先run()方法中的 target.run();不可能被调用,线程对象调用start()方法后运行的是重写后的run()方法中的内容。
    2、如果我们实例化Runnable对象,重写它的run()方法(注意哦!这里的run()方法不是Thread类中的run()方法),然后
    在创建Thread对象的时候把它传入,接下来用Thread对象调用start()方法执行的就是target.run(),也就是Runnable实例化后重写的run()方法的方法体了。
    3、如果在传入Runnable实例化对象后又重写了Thread类的run()方法,最终调用的是Thread类中重写的run()方法。

    Runnable和Thread在应用上的区别

    一个简单的买票程序
    一、写一个类继承Thread,重写run()方法,new若干线程执行买票程序。
    1、代码实现

    public class Test {	
    	    public static void main(String[] args)
    	    {
    	    	MyThread mt1 = new MyThread();
    	    	MyThread mt2 = new MyThread();
    	    	MyThread mt3 = new MyThread();
    	    	mt1.start();
    	    	mt2.start();
    	    	mt3.start();
    	    }
    }
    class MyThread extends Thread{
        private int ticket=10;
    	public void run() {	    
    		while(ticket>0)
    		{
    			System.out.println("卖出了ticket:"+ticket--);
    		}
    	}
    }
    

    2、运行结果
    在这里插入图片描述
    3、结果分析:每一张票都被买了3次,因为在每次new一个对象后,都会在java的堆中开辟一个新的空间来存储对象的信息,这就包括成员变量信息,在这里就是ticket变量,new了3个对象,就有3个地方存储了ticket值,彼此独立,因此每个线程执行着彼此独立的买票流程。
    4、解决方法:可以ticket变量加上static修饰,使得它变为被所有对象共享的静态变量,可以解决这个问题。当然上述方法并不能体现我们这次讨论的实质,根据结果分析我们可以知道在Thread重写run()方法这种方式下多线程不能共享资源,它们彼此独立。另外,使用Runnable的run()方法实现也可以解决这个问题,下面来看第二种方法。
    二、实现Runnable接口,作为参数传入Thread类。
    1、代码实现

    public class Test {	
    	    public static void main(String[] args)
    	    {
    	    	MyThread mt = new MyThread();
    	    	Thread th1 = new Thread(mt);
    	    	Thread th2 = new Thread(mt);
    	    	Thread th3 = new Thread(mt);
    	    	th1.start();
    	    	th2.start();
    	    	th3.start();
    	    }
    }
    class MyThread implements Runnable{
        private int ticket=10;
    	public void run() {	    
    		while(ticket>0)
    		{
    			System.out.println("卖出了ticket:"+ticket--);
    		}
    	}	
    }
    

    2、运行结果
    在这里插入图片描述
    3、结果分析:实现了10张票就卖出10次,因为通过前面的源码分析我们可以知道,每个线程都持有着Runnable实例化对象的引用,调用的都是这个对象的run()方法,因此是对同一个ticket变量进行操作。至于图中出现的顺序问题,是因为多线程操作的时候,修改变量的速度远大于打印出这个变量的速度,在改变了ticket值之后还没来得及打印出来,下一个线程就继续对变量进行操作了。
    总结:在多线程的应用中我们更加倾向于使用Runnable来实现run()方法,因为这可以使得多线程在执行相同的代码的同时操作相同成员变量(多线程共享资源)。因此,多线程的最佳打开方式是:写一个类继承Runnable接口,重写run()方法,再以Thread为媒介启动线程。

    展开全文
  • 彻底理解Runnable和Thread的区别

    万次阅读 多人点赞 2019-07-30 09:45:42
      在实际工作中,我们很可能习惯性地选择Runnable或Thread之一直接使用,根本没在意二者的区别,但在面试中很多自以为是的菜货面试官会经常而且非常严肃的问出:请你解释下Runnable或Thread的区别?尤其是新手就...
  • Runnable

    2021-01-04 14:59:51
    创建一个Runnable接口的实现类 package ThreadDemo01;/* @Author wanghongyuan @Date 2021/1/4 */ // 1.创建一个Runnable接口的实现类 public class RunnableImpl implements Runnable{ // 2.在实现类中重写...
  • Runnable任务

    2019-05-08 22:22:35
    任务 Runnable 定义了一个可以独立运行的代码片段,通常用于界面控件的延迟处理,比如为了避免同时占用某种资源造成冲突,有时则是为了反复间隔刷新界面从而产生动画效果。 运行 个任务也有多种形式,既能在 线程中...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 550,168
精华内容 220,067
关键字:

runnable