美文网首页Java 杂谈JavaIT面试
Java面试问题——线程全面详解总结

Java面试问题——线程全面详解总结

作者: Java_苏先生 | 来源:发表于2019-06-25 15:26 被阅读12次

    一、多线程是什么?为什么要用多线程?

    介绍多线程之前要介绍线程,介绍线程则离不开进程。

    首先进程 :是一个正在执行中的程序,每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元;

    线程:就是进程中的一个独立控制单元,线程在控制着进程的执行。一个进程中至少有一个进程。

    多线程:一个进程中不只有一个线程。

    为什么要用多线程

    • 为了更好的利用cpu的资源,如果只有一个线程,则第二个任务必须等到第一个任务结束后才能进行,如果使用多线程则在主线程执行任务的同时可以执行其他任务,而不需要等待;
    • 进程之间不能共享数据,线程可以;
    • 系统创建进程需要为该进程重新分配系统资源,创建线程代价比较小;
    • Java语言内置了多线程功能支持,简化了java多线程编程。

    二、线程的生命周期:

    新建 :从新建一个线程对象到程序start() 这个线程之间的状态,都是新建状态;

    就绪 :线程对象调用start()方法后,就处于就绪状态,等到JVM里的线程调度器的调度;

    运行 :就绪状态下的线程在获取CPU资源后就可以执行run(),此时的线程便处于运行状态,运行状态的线程可变为就绪、阻塞及死亡三种状态。

    等待/阻塞/睡眠 :在一个线程执行了sleep(睡眠)、suspend(挂起)等方法后会失去所占有的资源,从而进入阻塞状态,在睡眠结束后可重新进入就绪状态。

    终止 :run()方法完成后或发生其他终止条件时就会切换到终止状态。

    三、创建线程的方法(还有其他方法):

    1、继承Thread类:

    步骤: 、定义类继承Thread;

    • 复写Thread类中的run方法;

    目的:将自定义代码存储在run方法,让线程运行

    • 调用线程的start方法:

    该方法有两步:启动线程,调用run方法。

    2、实现Runnable接口:接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称为run 的无参方法。

    实现步骤: 、定义类实现Runnable接口

    • 覆盖Runnable接口中的run方法
      将线程要运行的代码放在该run方法中。
    • 通过Thread类建立线程对象。
    • 将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
      自定义的run方法所属的对象是Runnable接口的子类对象。所以要让线程执行指定对象的run方法就要先明确run方法所属对象
    • 调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。

    3、通过Callable和Future创建线程:

    实现步骤

    • 创建Callable接口的实现类,并实现call()方法,改方法将作为线程执行体,且具有返回值。
    • 创建Callable实现类的实例,使用FutrueTask类进行包装Callable对象,FutureTask对象封装了Callable对象的call()方法的返回值
    • 使用FutureTask对象作为Thread对象启动新线程。
    • 调用FutureTask对象的get()方法获取子线程执行结束后的返回值。

    四、继承Thread类和实现Runnable接口、实现Callable接口的区别。

    继承Thread:线程代码存放在Thread子类run方法中。

    • 优势:编写简单,可直接用this.getname()获取当前线程,不必使用Thread.currentThread()方法。
    • 劣势:已经继承了Thread类,无法再继承其他类。

    实现Runnable:线程代码存放在接口的子类的run方法中。

    • 优势:避免了单继承的局限性、多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。
    • 劣势:比较复杂、访问线程必须使用Thread.currentThread()方法、无返回值。

    实现Callable

    • 优势:有返回值、避免了单继承的局限性、多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。
    • 劣势:比较复杂、访问线程必须使用Thread.currentThread()方法建议使用实现接口的方式创建多线程。

    五、线程状态管理

    1、线程睡眠---sleep:

    线程睡眠的原因:线程执行的太快,或需要强制执行到下一个线程。

    线程睡眠的方法(两个):sleep(long millis)在指定的毫秒数内让正在执行的线程休眠。

    sleep(long millis,int nanos)在指定的毫秒数加指定的纳秒数内让正在执行的线程休眠。

    线程睡眠的代码演示:

    public class SynTest {
        public static void main(String[] args) {
            new Thread(new CountDown(),"倒计时").start();
        }
    }
    class CountDown implements Runnable{
        int time = 10;
        public void run() {
            while (true) {
                if(time>=0){
                    System.out.println(Thread.currentThread().getName() + ":" + time--);
                    try {
                        Thread.sleep(1000);
                        //睡眠时间为1秒
                    }
                    catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    

    每隔一秒则会打印一次,打印结果为:

    倒计时:10
    倒计时:9
    倒计时:8
    倒计时:7
    倒计时:6
    倒计时:5
    倒计时:4
    倒计时:3
    倒计时:2
    倒计时:1
    倒计时:0
    

    扩展
    Java线程调度是Java多线程的核心,只有良好的调度,才能充分发挥系统的性能,提高程序的执行效率。但是不管程序员怎么编写调度,只能最大限度的影响线程执行的次序,而不能做到精准控制。因为使用sleep方法之后,线程是进入阻塞状态的,只有当睡眠的时间结束,才会重新进入到就绪状态,而就绪状态进入到运行状态,是由系统控制的,我们不可能精准的去干涉它,所以如果调用Thread.sleep(1000)使得线程睡眠1秒,可能结果会大于1秒。

    总结

    同步的前提

    1. 必须要有两个或者两个以上的线程。
    2. 必须是多个线程使用同一个锁。
    3. 必须保证同步中只能有一个线程在运行。
    4. 只能同步方法,不能同步变量和类。
    5. 不必同步类中所有方法,类可以拥有同步和非同步的方法。
    6. 如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。
    7. 线程睡眠时,它所持的任何锁都不会释放。
      • 好处:解决了多线程的安全问题。
      • 弊端:多个线程需要判断,消耗资源,降低效率。

    如何找问题

    1. 明确哪些代码是多线程运行代码。
    2. 明确共享数据。
    3. 明确多线程运行代码中哪些语句是操作共享数据的。

    写在最后

    • 第一:看完点赞,感谢您对作者的认可;
    • ...
    • 第二:随手转发,分享知识,让更多人学习到;
    • ...
    • 第三:记得点关注,每天更新的!!!
    • ...

    相关文章

      网友评论

        本文标题:Java面试问题——线程全面详解总结

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