美文网首页Java 杂谈java 知识点
Java 多线程 Thread 和 Runnable

Java 多线程 Thread 和 Runnable

作者: Tim在路上 | 来源:发表于2019-09-19 10:26 被阅读0次

    Thread 和 Runnable 区别

    多线程是并行计算实现的方式, 但是在单cpu中实际上没有真正的并行,只不过是多个任务通过cpu的快速轮转,产生多任务同一时间运行的错觉.而其中的任务就是进程. (当然多核CPU,并行还是真实存在的). 一个进程中至少有一个线程,线程运行在进程中,但是cpu的调度的是进程中的线程,所以一个线程可以占据多个cpu核.

    每一个线程有自己的局部变量,程序计数器,生命周期.

    eg. 操作系统创建一个JVM进程,所有的java线程都是在jvm进程中,但是线程是最小的调度单位,CPU调度的是进程中的线程.一个线程可以占据多个核. 另外,Java可以进行多进程编程,启动一个新的子进程,就相当于一个新的虚拟机.

    但是python中由于GIL锁导致一个线程,只能运行在一个核上也就相当于串行化多进程.

    1.生命周期

    • 线程new状态

    new 创建一个Thread是只是创建了一个线程的实例,依然是java的对象, 不处于执行状态,为new状态.

    • 线程runnable状态

    线程通过start启动并不会立即执行,这个时期属于runnable状态, runable状态必须听令于CPU的调度,才会进入running状态.
    同时runnable状态不会直接进入blocked和terminated状态,runnable只会意外终止和running状态.

    • 线程running状态

    cpu轮询选中线程进入running状态, stop进入 terminated状态,调用sleep,或wait线程进入waitSet中,IO操作,锁,进入blocked状态.
    cpu调度器放弃线程回到runnable,线程主动调用yield,放弃cpu执行权,进入runnable

    • 线程blocked状态

    blocked 可以直接stop进入terminated状态 , JVM Crash
    阻塞结束,完成休眠,唤醒,拿到锁资源进入runnable

    • 线程Terminated
      线程正常结束生命周期
      线程意外结束
      Jvm Crash 线程结束

    2.run()和start()方法的关系

    Thread被构造为New状态,事实上threadStatus 内部属性为0

    不能两次启动Thread,否则就会出现IllegalThreadStateException异常

    线程被启动后加入到ThreadGroup中,线程状态受到ThreadGroup影响

    一个线程的生命周期结束,再调用start方法是不允许的,Terminated无法回到runnable/running状态.

    thread.start()
    
    TimeUnit.SECONDS.sleep(2)
    
    thread.start()
    

    Thread中的run方法就是空的实现,run和start采用模板的设计方法,run本身就是空的方法,留实现类实现逻辑,start会调用线程的启动,运行,run,以及关闭的操作.

    3. Thread run 和 Runnable run 区别

    首先,实现线程的执行单元有两种方式,

    1. 重写 Thread 的 run 方法
    2. 实现Runnable接口的 run 并将 Runnable 实例用作构造 Thread的参数

    无论Runnable和Thread都是实现Runnable的接口

    实质:Thread 类中调用 Runnable 实现的重写run接口

    Thread 的 run 方法是不能共享的,A 线程不能把B的run当自己的资源,实现资源共享使用static

    Runnable 只要使用同一个Runnable,构造不同的Thread实例,就可以资源共享.

    简而言之: Thread负责线程本身的职责控制, Runnable负责逻辑单元执行

    Thread API

    任何一个线程都是由另一个线程创建的,main线程由JVM创建的,程序里面的父线程都是main线程.

    main 线程 所在的 ThreadGroup 称为 main

    构造一个线程没有指定 ThreadGroup 会和父进程同属一个ThreadGroup

    创建 Thread时可以设置StackSize ,stackSize 越大代表线程的递归深度越深,stackSize越小创建线程的数量越多

    java虚拟机栈和程序计数器都是线程私有的,生命周期和线程相同

    堆内存是被所有线程共享的内存区域.

    元空间,是堆内存的一部分,JVM为每一个类加载器分配一块内存列表,进行线性分配,块的大小跟类加载器的种类相关,类加载器具备回收条件,之前会单独回收类加载器空间,现在直接把相对应的元空间回收,减少内存碎片.
    

    进程的内存大小 = 堆内存 + 线程数量 + 栈内存

    1. 守护线程

    如果JVM进程中没有一个非守护线程,那么JVM就会退出,守护线程可以自动结束生命周期,守护线程主要是为了进行后天工作.

    • 设置守护线程 setDaemon 方法, true 代表守护线程

    • 父线程是守护线程,子线程是守护,反之亦然

    守护线程必须在启动前设置才有效

    2. 线程yield

    yield 会提醒调度器放弃当前的CPU资源,如果cpu资源不紧张则会忽略这种提醒
    yield 让线程从running 切换到 runnable

    yield 只是一个提示, cpu调度器并不会担保每次满足yield提示.

    3. 线程priority

    优先级也是hint操作,一般优先级高的可以优先获得cpu,但是闲时优先级高低不会有任何作用.
    如果优先级大于组的优先级,那么指定的优先级则会失效,则是组的最大优先级.

    main 的优先级是5,它派生出来的程序的优先级都是5

    4. 线程上下文类加载器

    ClassLoader getContextClassLoader() 获取线程的上下文类加载器,线程由哪一个类加载的,默认与父线程保持一致.

    ClassLoader setContextClassLoader()
    打破java类加载器的委托机制.

    5. 线程interrupt

    一个线程被 wait,sleep,join 则线程会进入阻塞状态
    如果另外一个线程调用当前线程的interrupted 则可以打断阻塞
    抛出InterruptedException异常

    一个线程内部存在interrupt flag 标识,一个线程被interrupt,则flag被设置,如果正在执行可中断方法,调用interrupt,那么flag会被清除.

    必须如果run 中用 wait,sleep,join 则在执行完interrupt后,状态任然为 false,没有可中断,则中断标识为true.

    6. 线程join

    Thread 的 join同样是可中断方法, 如果其他线程执行该线程interrupt方法,同样会捕捉到中断信号,并将标识位擦除.

    join某个线程A,会使当前的线程B处于等待,直到A生命周期结束,期间B处于blocked状态.
    join在start后执行

    7. 线程stop

    线程Stop后不会释放掉monitor锁.

    线程最好的退出方式捕获中断异常进行退出, t.interrupt();
    如果在线程中执行某个可中断方法,则可以通过捕获中断信号来决定是否退出.
    即在catch中决定是否退出

    8. 使用volatile开关控制

    由于线程的interrupt标识可能被擦除,使用volatile修饰开关flag关闭线程

    private volatile boolean closed = false;
    while (!closed && !isTnterrupted()){
    
        
    }
    

    相关文章

      网友评论

        本文标题:Java 多线程 Thread 和 Runnable

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