zoukankan      html  css  js  c++  java
  • 线程专题 -- 线程的创建,状态,工作过程,常见方法

    线程(Thread)是并发编程的基础,一般会作为并发编程的起始问题,用于引出更多的关于并发编程的面试问题。对于线程的掌握程度也决定了你对并发编程的掌握程度。

    什么是进程?线程?区别?

    1)进程是一个独立的运行环境,它可以被看作是一个程序或者一个应用。而线程是在进程中执行的一个任务。eg:打开360安全卫士,它本身是一个程序,也是一个进程,它里面有杀毒,清理垃圾,电脑加速等功能,当你点击杀毒的时候,杀毒任务就相当于一个线程。

     2)进程是操作系统进行资源分配的基本单位,而线程是操作系统进行调度的基本单位。

     3)进程让操作系统的并发性成为可能,而线程让进程的内部并发成为可能。

    补充理解:

    进程与线程的区别

    --https://jingyan.baidu.com/article/624e74598efcc834e9ba5a66.html

    5个步骤,教你瞬间明白线程和线程安全

    -- https://baijiahao.baidu.com/s?id=1610396903519844310&wfr=spider&for=pc

    线程安全?

    定义:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。

    出现线程不安全的原因是什么?

    如果我们创建的多个线程,存在着共享数据,那么就有可能出现线程的安全问题:当其中一个线程操作共享数据时,还未操作完成,另外的线程就参与进来,导致对共享数据的操作出现问题。

    线程不安全解决办法?

    要求一个线程操作共享数据时,只有当其完成操作共享数据,其它线程才有机会执行共享数据。java提供了两种方式来实现同步互斥访问:synchronized和Lock。

    创建线程的方式?区别?

    1)继承Thread类;

    2)实现Runnable接口;

    3)实现Callable接口;

    4)  通过线程池创建线程;

    前三种方式的区别?

    Java中,类仅支持单继承,如果一个类继承了Thread类,就无法再继承其它类,因此,如果一个类既要继承其它的类,又必须创建为一个线程,就可以使用实现Runable接口的方式。

    使用实现Runable接口的方式创建的线程可以处理同一资源,实现资源的共享。

    使用实现Callable接口的方式创建的线程,可以获取到线程执行的返回值、是否执行完成等信息。

    Java中Runnable和Callable有什么不同?

    Runnable和Callable都是创建线程的方式。Runnable从JDK1.0开始就有了,Callable是在JDK1.5增加的。

    1)实现Callable接口的任务线程能返回执行结果;而实现Runnable接口的任务线程不能返回结果;

    2)Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;

    线程的状态有哪些?

    线程的状态在 JDK 1.5 之后以枚举的方式被定义在 Thread 的源码中,它总共包含以下 6 个状态:

    线程状态的源代码如下:

    (1)NEW,新建状态,线程被创建出来,但尚未启动时的线程状态;

    (2)RUNNABLE,就绪状态,表示可以运行的线程状态,它可能正在运行,或者是在排队等待操作系统给它分配 CPU 资源;

    (3)BLOCKED,阻塞等待锁的线程状态,表示处于阻塞状态的线程正在等待监视器锁,比如等待执行 synchronized 代码块或者使用 synchronized 标记的方法;

    (4) WAITING,等待状态,一个处于等待状态的线程正在等待另一个线程执行某个特定的动作,比如,一个线程调用了Object.wait()方法,那它就在等待另一个线程调用Object.notify()或 Object.notifyAll() 方法;

    (5)TIMED_WAITING,计时等待状态,和等待状态(WAITING)类似,它只是多了超时时间,比如调用了有超时时间设置的方法Object.wait(longtimeout)和Thread.join(long timeout) 等这些方法时,它才会进入此状态;

    (6)TERMINATED,终止状态,表示线程已经执行完成。

    如果想要确定线程当前的状态,可以通过 getState() 方法,并且线程在任何时刻只可能处于 1 种状态。

    线程是如何工作的?(工作过程?)

      首先先要创建线程并指定线程需要执行的业务方法,然后再调用线程的start()方法,此时线程就从NEW(新建)状态变成了RUNNABLE(就绪)状态,然后线程会判断要执行的方法中有没有 synchronized 同步代码块,如果有并且其他线程也在使用此锁,那么线程就会变为 BLOCKED(阻塞等待锁的线程)状态,当其他线程使用完此锁之后,线程会继续执行剩余的方法。当遇到Object.wait()或Thread.join()方法时,线程会变为WAITING(等待状态)状态,如果是带了超时时间的等待方法,那么线程会进入TIMED_WAITING(计时等待)状态,当有其他线程执行了notify()或notifyAll()方法之后,线程被唤醒继续执行剩余的业务方法,直到方法执行完成为止,此时整个线程的流程就执行完了。

    start() 和run() 方法有什么区别?

    源码分析:

    (1) start() 方法属于 Thread 自身的方法,并且使用了 synchronized 来保证线程安全。它的作用是启动一个新线程。通过start()方法来启动的新线程,从NEW(新建)状态变成了处于RUNNABLE(就绪)状态,并没有运行,一旦得到cpu时间片,就开始执行相应线程的run()方法,这里方法run()称为线程体,它包含了线程要执行的业务方法,run方法运行结束,此线程随即终止。

    源码如下:

     (2)run() 方法为 Runnable 的抽象方法,必须由调用类重写此方法,重写的 run() 方法其实就是此线程要执行的业务方法;run() 就和普通的成员方法一样,可以被重复调用,如果直接调用run方法,并不会启动新线程!程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行。

    源码如下:

     Thred实现Runnable接口,重写run方法,源码如下

     

    区别

     (1) start() 可以启动一个新线程,让线程从NEW状态转换成RUNNABLE状态,而run()不能,只是一个普通的方法。

     (2) start()不能被重复调用(否则会抛出 java.lang.IllegalStateException),而 run() 方法可以进行多次调用,因为它只是一个普通的方法而已。

     (3)start()中的run代码可以不执行完就继续执行下面的代码,即进行了线程切换。直接调用run方法必须等待其代码全部执行完才能继续执行下面的代码。

    线程的优先级有什么用?该如何设置?

    线程的优先级可以理解为线程抢占 CPU 时间片的概率,优先级越高的线程优先执行的概率就越大,但并不能保证优先级高的线程一定先执行。

    在程序中我们可以通过 Thread.setPriority() 来设置优先级,setPriority() 源码如下:

    线程的常用方法?

     join()

    在一个线程中调用other.join(),这时候当前线程会让出执行权给other线程,直到other线程执行完或者过了超时时间之后再继续执行当前线程,join() 源码如下:

    从源码中可以看出 join() 方法底层还是通过 wait() 方法来实现的。

    yield()

    看 Thread 的源码可以知道 yield() 为本地方法,也就是说 yield() 是由 C 或 C++ 实现的,源码如下:

    yield() 方法表示向线程调度器提示当前线程愿意出让 CPU 使用权,但是线程调度器可能会忽略这个暗示。

    Java多线程中调用wait() 和 sleep()方法有什么不同?

    sleep()和wait()都是使线程暂停执行一段时间的方法。二者区别为:

    1)原理不同(面试时可不答,偏重2,3,4)

    sleep()方法是Thread类的静态方法,是线程用来控制自身流程的

    wait()方法是Object类的方法,用于线程间的通信。

    2)对锁的处理机制不同

    调用wait()的时候方法会释放当前持有的锁,而sleep方法不会释放锁。

    3)使用地方不同

    sleep方法则可以放在任何地方使用,而wait()方法必须放在同步方法或者同步代码块中使用。

    4)异常处理

    sleep()方法必须捕获异常,而wait()、notify()、notifyAll()不需要捕获异常。

    补充说明:由于sleep不会释放锁标志,容易导致死锁问题的发生,一般情况下,不推荐使用sleep()方法,而推荐使用wait()方法。

    Java中notify 和 notifyAll有什么区别?

     调用notify时,只有一个等待线程会被唤醒而且它不能保证哪个线程会被唤醒,这取决于线程调度器。如果你调用notifyAll方法,那么等待该锁的所有线程都会被唤醒。

     Java中interrupted 和 isInterruptedd方法的区别?

      interrupted() 和 isInterrupted()的主要区别是前者会将中断状态清除而后者不会。

      Java多线程的中断机制是用内部标识来实现的,调用Thread.interrupt()来中断一个线程就会设置中断标识为true。当中断线程调用静态方法Thread.interrupted()来检查中断状态时,中断状态会被清零。而非静态方法isInterrupted()用来查询其它线程的中断状态且不会改变中断状态标识。简单的说就是任何抛出InterruptedException异常的方法都会将中断状态清零。无论如何,一个线程的中断状态有有可能被其它线程调用中断来改变。

    什么是ThreadLocal?

      ThreadLocal用于创建线程的本地变量,我们知道一个对象的所有线程共享它的全局变量,所以这些变量是非线程安全的,我们可以使用同步技术。但是当我们不想使用同步的时候,我们可以选择ThreadLocal变量。每个线程都会拥有他们自己的Thread变量,它们可以使用get()/set()方法去获取他们的默认值或者在线程内部改变他们的值。

      有三个线程T1,T2,T3,怎么确保它们按顺序执行?

    在多线程中有多种方法让线程按特定顺序执行,你可以用线程类的join()方法在一个线程中启动另一个线程,另外一个线程完成该线程继续执行。为了确保三个线程的顺序你应该先启动最后一个(T3调用T2,T2调用T1),这样T1就会先完成而T3最后完成。

     如何创建守护线程?

      使用Thread类的setDaemon(true)方法可以将线程设置为守护线程,需要注意的是,需要在调用start()方法前调用这个方法,否则会抛出IllegalThreadStateException异常。

    /**
     * @author liao.wenhui
     * @date 2019/7/15 15:13
     */
    public class DaemonThread {
        public static void main(String[] args) {
            Thread daemonThread = new Thread(new Runnable() {
                @Override
                public void run() {
    
                }
            });
    
            //设置守护线程
            daemonThread.setDaemon(true);
            daemonThread.start();
        }
    }

    前提知识:

      守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件(百度百科)。

      Java线程分为两类分别为daemon线程(守护线程)和User线程(用户线程),在JVM启动时候会调用main函数,main函数所在的线程是一个用户线程,这个是我们可以看到的线程,其实JVM内部同时还启动了好多守护线程,比如垃圾回收线程。那么守护线程和用户线程有什么区别那?区别之一是当最后一个非守护线程结束时候,JVM会正常退出,而不管当前是否有守护线程,也就是说守护线程是否结束并不影响JVM的退出。言外之意是只要有一个用户线程还没结束正常情况下JVM就不会退出。

    什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing)?

      线程调度器是一个操作系统服务,它负责为Runnable状态的线程分配CPU时间。一旦我们创建一个线程并启动它,它的执行便依赖于线程调度器的实现。

      时间分片是指将可用的CPU时间分配给可用的Runnable状态的线程的过程。分配CPU时间可以基于线程优先级或者线程等待的时间。线程调度并不受到Java虚拟机控制,所以由应用程序来控制它是更好的选择(即最好不要让你的程序依赖于线程的优先级)。

    什么是FutureTask?

      在Java并发程序中FutureTask表示一个可以取消的异步运算。它有启动和取消运算、查询运算是否完成和取回运算结果等方法。只有当运算完成的时候结果才能取回,如果运算尚未完成get方法将会阻塞。一个FutureTask对象可以对调用了Callable和Runnable的对象进行包装,由于FutureTask也是调用了Runnable接口所以它可以提交给Executor来执行。

    在多线程中,什么是上下文切换?

    上下文切换是存储和恢复CPU状态的过程,它使得线程执行能够从中断点恢复执行。是多任务操作系统和多线程环境的基本特征。 

    并发编程三要素

    (1) 原子性:程序中的所有操作是不可中断的,要么全部执行成功要么全部执行失败。

    (2) 有序性:程序执行的顺序按照代码的先后顺序执行。(处理器可能会对指令进行重排序)

    (3) 可见性:当多个线程访问同一个变量时,如果其中一个线程对其作了修改,其他线程能立即获取到最新的值。

    参考/好文:

    Java 面试真题及源码 34 讲

    -- https://kaiwu.lagou.com/course/courseInfo.htm?courseId=59

  • 相关阅读:
    hadoop下生成echarts关系图
    MongoDB实现增删查方法
    Hadoop中配置环境后重启失效解决方法
    Ubuntu中linux虚拟机全屏
    第二周第三天
    构建之法阅读笔记02
    第二周第二天
    第二周第一天
    学习进度条第五周
    第一周第七天
  • 原文地址:https://www.cnblogs.com/liaowenhui/p/11184884.html
Copyright © 2011-2022 走看看