2024年java面试--多线程(1)

系列文章目录

  1. 2024年java面试(一)–spring篇
  2. 2024年java面试(二)–spring篇
  3. 2024年java面试(三)–spring篇
  4. 2024年java面试(四)–spring篇

文章目录

  • 系列文章目录
  • 线程调度
    • 线程五种状态
    • 线程状态切换
    • wait和sleep区别
    • 进程和线程区别
  • 实现多线程的四种方式
    • 继承Thread类实现多线程
    • 实现Runnable(优先使用)
    • 实现 Runnable 接口(优先使用)
    • 实现Callable接口


线程调度

线程五种状态

线程状态:创建、就绪、运行、阻塞、死亡

1.新建状态(New) :线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。

2.就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。

3.运行状态(Running):线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。

4.阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(1)、等待阻塞:运行的线程执行wait方法,该线程会释放占用的所有资源,JVM会把该线程放入"等待池"中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify或notifyAll方法才能被唤醒,wait是object类的方法
(2)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则VM会把该线程放入"锁池"中。
(3)、其他阻塞:运行的线程执行sleep或join方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep状态超时、join等待线程终止或者超时、或者l/O处理完毕时,线程重新转入就绪状态。sleep是Thread类的方法

5.死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

2024年java面试--多线程(1)_第1张图片

线程状态切换

方法 作用 区别
start 启动线程,由虚拟机自动调度执行run()方法 线程处于就绪状态
run 线程逻辑代码块处理,JVM调度执行 线程处于运行状态
sleep 让当前正在执行的线程休眠(暂停执行) 不释放锁
wait 使得当前线程等待 释放同步锁
notify 唤醒在此对象监视器上等待的单个线程 唤醒单个线程
notifyAll 唤醒在此对象监视器上等待的所有线程 唤醒多个线程
yiled 停止当前线程,让同等优先权的线程运行 用Thread类调用
join 使当前线程停下来等待,直至另一个调用join方法的线程终止 用线程对象调用

yield () 执行后线程直接进入就绪状态,马上释放了cpu的执行权,但是依然保留了cpu的执行资格,所以有可能cpu下次进行线程调度还会让这个线程获取到执行权继续执行

join () 执行后线程进入阻塞状态,例如在线程B中调用线程A的join (),那线程B会进入到阻塞队列,直到线程A结束或中断线程

wait和sleep区别

  • wait 方法必须在 synchronized 保护的代码中使用,而 sleep 方法并没有这个要求。
  • wait 方法会主动释放锁,在同步代码中执行 sleep 方法时,并不会释放锁。
  • wait 方法意味着永久等待,直到被中断或被唤醒才能恢复,不会主动恢复,sleep 方法中会定义一个时间,时间到期后会主动恢复。
  • wait/notify 是 Object 类的方法,而 sleep 是 Thread 类的方法。

进程和线程区别

1.根本区别:进程是操作系统进行资源分配的最小单元,线程是操作系统进行运算调度的最小单元。

2.从属关系不同:进程中包含了线程,线程属于进程。

3.开销不同:进程的创建、销毁和切换的开销都远大于线程。

4.拥有资源不同:每个进程有自己的内存和资源,一个进程中的线程会共享这些内存和资源。

5.控制和影响能力不同:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。

6.CPU利用率不同:进程的CPU利用率较低,因为上下文切换开销较大,而线程的CPU的利用率较高,上下文的切换速度快。

7.操纵者不同:进程的操纵者一般是操作系统,线程的操纵者一般是编程人员。


实现多线程的四种方式

继承Thread类实现多线程

继承类Thread是支持多线程的功能类,只要创建一个子类就可以实现多线程的支持。

所有的java程序的起点是main方法,所以线程一定有自己的起点,那这个起点就是run方法;因为多线程的每个主体类之中必须重写Thread的run方法。

这个run方法没有返回值,那就说明线程一旦开始就一直执行不能返回内容。

多线程启动的唯一方法是调用Thread的start方法,如果调用的是run方法就是普通run方法的调用(调用此方法执行的是run方法体)。

总结:使用Thread类的start方法不仅仅启动多线程的执行代码,还要从不同操作系统中分配资源。

步骤:

1.创建一个继承于Thread类的子类

2.重写Thread类的run() --> 将此线程执行的操作声明在run()中

3.创建Thread类的子类的对象

4.通过此对象调用start():start()作用①启动当前线程 ② 调用当前线程的run()

继承Thread类(java不支持多继承)

public class ExtendsThread extends Thread {
    @Override
    public void run() {System.out.println('用Thread类实现线程');}
}

实现Runnable(优先使用)

Java具有单继承局限,所有的Java程序针对类的继承都应该是回避,那么线程也一样,为了解决单继承的限制,因此才有Runnable接口。

使用方法:让一个类实现Runnable接口即可,并且也需要覆写run()方法。

疑问:但是此接口只有run方法,没有start方法,怎么启动多线程呢?

不管任何情况下,如果要想启动多线程一定要依靠Thread类完成,在Thread类中有参数是Runnable参数的构造方法:

Thread(Runnable target) 接收的是Runnable接口

可以创建一个参数是Runnable实现类的Thread类,调用start方法启动。

总结:实现Runnable接口来写多线程的业务类,用Thread来启动多线程。

实现 Runnable 接口(优先使用)

public class RunnableThread implements Runnable {
    @Override
    public void run() {System.out.println('用实现Runnable接口实现线程');}
}

实现Callable接口

实现Callable接口(有返回值可抛出异常)

步骤:

1.实现Callable接口

2.重写里面的Call方法(注意是Call不是Run)

3.创建Callable实现类的对象

4.将实现类对象作为参数传递给FutureTask构造函数

5.将FutureTask对象作为参数传递给Thread构造函数(因为FutureTask实现了Runnable接口,所以可以这么传)

6.调用Thread类的start方法

//class CallableTask implements Callable {
    //@Override
    //public Integer call() throws Exception { return new Random().nextInt();}
//}
@Override
    public Object call() throws Exception {
        System.out.println("CallableImpl");
        return "我是Call方法的返回值";
    }public static void main(String[] args) {
        CallableImpl callable=new CallableImpl();
        FutureTask<Object> futureTask=new FutureTask<>(callable);
        Thread thread=new Thread(futureTask);
       
        需要注意一件事:
        FutureTask类中的get方法获取返回值只能执行一次
        而且,如果使用了这个方法但是线程还没有运行到可以返回的那行代码,那么就会一直阻塞
        比如如果我在这里执行了如下代码:
        Object result=futureTask.get();
        那么就永远阻塞了
        当然,我更想说的是,如果你使用的是这种方法创建线程并且需要返回值的话,里面就别写死循环
        否则就是死锁在召唤
            
        thread.start();
        try {
            Object result=futureTask.get();
            System.out.println(result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?

1.call()可以返回值的。

2.call()可以抛出异常,被外面的操作捕获,获取异常的信息

3.Callable是支持泛型的

你可能感兴趣的:(java,java,面试,开发语言)