2023-JavaSE最新整理面试题-IO和多线程专题

Posted 波波烤鸭

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2023-JavaSE最新整理面试题-IO和多线程专题相关的知识,希望对你有一定的参考价值。

Java基础面试题

一、IO和多线程专题

1.介绍下进程和线程的关系

进程:一个独立的正在执行的程序

线程:一个进程的最基本的执行单位,执行路径

多进程:在操作系统中,同时运行多个程序

多进程的好处:可以充分利用CPU,提高CPU的使用率**

多线程:在同一个进程(应用程序)中同时执行多个线程

多线程的好处:提高进程的执行使用率,提高了CPU的使用率**

注意:

  1. 在同一个时间点一个CPU中只可能有一个线程在执行
  2. 多线程不能提高效率、反而会降低效率,但是可以提高CPU的使用率
  3. 一个进程如果有多条执行路径,则称为多线程程序
  4. Java虚拟机的启动至少开启了两条线程,主线程和垃圾回收线程
  5. 一个线程可以理解为进程的子任务

2.说说Java中实现多线程的几种方法

创建线程的常用三种方式:

  1. 继承Thread类
  2. 实现Runnable接口
  3. 实现Callable接口(JDK1.5>=)
  4. 线程池方式创建

  通过继承Thread类或者实现Runnable接口、Callable接口都可以实现多线程,不过实现Runnable
接口与实现Callable接口的方式基本相同,只是Callable接口里定义的方法返回值,可以声明抛出异
常而已。因此将实现Runnable接口和实现Callable接口归为一种方式。这种方式与继承Thread方式
之间的主要差别如下。

继承Thread类

实现的步骤:

  1. 创建Thread类的子类
  2. 重写run方法
  3. 创建线程对象
  4. 启动线程

案例代码

package com.bobo.thread;

public class ThreadDemo02 

    /**
     * 线程的第一种实现方式
     *     通过创建Thread类的子类来实现
     * @param args
     */
    public static void main(String[] args) 
        System.out.println("main方法执行了1...");
        // Java中的线程 本质上就是一个Thread对象
        Thread t1 = new ThreadTest01();
        // 启动一个新的线程
        t1.start();
        for(int i = 0 ; i< 100 ; i++)
            System.out.println("main方法的循环..."+i);
        
        System.out.println("main方法执行结束了3...");
    


/**
 * 第一个自定义的线程类
 *    继承Thread父类
 *    重写run方法
 */
class ThreadTest01 extends Thread

    @Override
    public void run() 
        System.out.println("我们的第一个线程执行了2....");
        for(int i = 0 ; i < 10 ; i ++)
            System.out.println("子线程:"+i);
        
    


注意点:

  1. 启动线程是使用start方法而不是run方法
  2. 线程不能启动多次,如果要创建多个线程,那么就需要创建多个Thread对象

实现Runnable接口

  在第一种实现方式中,我们是将线程的创建和线程执行的业务都封装在了Thread对象中,我们可以通过Runable接口来实现线程程序代码和数据有效的分离。

Thread(Runnable target)
// 分配一个新的 Thread对象。

实现的步骤:

  1. 创建Runable的实现类
  2. 重写run方法
  3. 创建Runable实例对象(通过实现类来实现)
  4. 创建Thread对象,并把第三部的Runable实现作为Thread构造方法的参数
  5. 启动线程
package com.bobo.runable;

public class RunableDemo01 

    /**
     * 线程的第二种方式
     *     本质是创建Thread对象的时候传递了一个Runable接口实现
     * @param args
     */
    public static void main(String[] args) 
        System.out.println("main执行了...");
        // 创建一个新的线程  Thread对象
        Runnable r1 = new RunableTest();
        Thread t1 = new Thread(r1);
        // 启动线程
        t1.start();
        System.out.println("main结束了...");
    


/**
 * 线程的第二种创建方式
 *   创建一个Runable接口的实现类
 */
class RunableTest implements Runnable

    @Override
    public void run() 
        System.out.println("子线程执行了...");
    

实现Runable接口的好处:

  1. 可以避免Java单继承带来的局限性
  2. 适合多个相同的程序代码处理同一个资源的情况,把线程同程序的代码和数据有效的分离,较好的体现了面向对象的设计思想

Callable的方式

  前面我们介绍的两种创建线程的方式都是重写run方法,而且run方法是没有返回结果的,也就是main方法是不知道开启的线程什么时候开始执行,什么时候结束执行,也获取不到对应的返回结果。而且run方法也不能把可能产生的异常抛出。在JDK1.5之后推出了通过实现Callable接口的方式来创建新的线程,这种方式可以获取对应的返回结果

package com.bobo.callable;

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class CallableDemo01 

    /**
     * 创建线程的第三种实现方式:
     *    Callable方式
     */
    public static void main(String[] args) throws  Exception 
        // 创建一个Callable实例
        Callable<Integer> callable = new MyCallable();
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        // 获取一个线程 肯定是要先创建一个Thread对象  futureTask本质上是Runable接口的实现
        Thread t1 = new Thread(futureTask);
        System.out.println("main方法start....");
        t1.start(); // 本质还是执行的 Runable中的run方法,只是 run方法调用了call方法罢了
        // 获取返回的结果
        System.out.println(futureTask.get()); // 获取开启的线程执行完成后返回的结果
        System.out.println("main方法end ....");

    


/**
 * 创建Callable的实现类
 *    我们需要指定Callable的泛型,这个泛型是返回结果的类型
 */
class MyCallable implements Callable<Integer>

    /**
     * 线程自动后会执行的方法
     * @return
     * @throws Exception
     */
    @Override
    public Integer call() throws Exception 
        int sum = 0;
        for(int i = 1 ; i <= 100 ; i ++)
            sum += i;
        
        return sum;
    


实现Runnable接口和实现Callable接口的区别:

  1. Runnable是自从java1.1就有了,而Callable是1.5之后才加上去的
  2. Callable规定的方法是call(),Runnable规定的方法是run()
  3. Callable的任务执行后可返回值,而Runnable的任务是不能返回值(是void)
  4. call方法可以抛出异常,run方法不可以
  5. 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
  6. 加入线程池运行,Runnable使用ExecutorService的execute方法,Callable使用submit方法。

其实Callable接口底层的实现就是对Runable接口实现的封装,线程启动执行的也是Runable接口实现中的run方法,只是在run方法中有调用call方法罢了

3.如何停止一个正在运行的线程

**设置标志位:**如果线程的run方法中执行的是一个重复执行的循环,可以提供一个标记来控制循环是否继续

public class FunDemo02 

    /**
     * 练习2:设计一个线程:运行10秒后被终止(掌握线程的终止方法)
     * @param args
     */
    public static void main(String[] args)  throws Exception
        MyRunable02 runnable = new MyRunable02();
        new Thread(runnable).start();
        Thread.sleep(10000); // 主线程休眠10秒钟
        runnable.flag = false;
        System.out.println("main、  end ...");
    


class MyRunable02 implements Runnable

    boolean flag = true;

    @Override
    public void run() 
        while(flag)
            try 
                Thread.sleep(1000);
                System.out.println(new Date());
             catch (InterruptedException e) 
                e.printStackTrace();
            
        
        System.out.println(Thread.currentThread().getName() + " 执行完成");
    

利用中断标志位: 在线程中有个中断的标志位,默认是false,当我们显示的调用 interrupted方法或者isInterrupted方法是会修改标志位为true。我们可以利用此来中断运行的线程。

package com.bobo.fundemo;

public class FunDemo07 extends Thread

    public static void main(String[] args) throws InterruptedException 
        Thread t1 = new FunDemo07();
        t1.start();
        Thread.sleep(3000);
         t1.interrupt(); // 中断线程 将中断标志由false修改为了true
        // t1.stop(); // 直接就把线程给kill掉了
        System.out.println("main .... ");
    

    @Override
    public void run() 
        System.out.println(this.getName() + " start...");
        int i = 0 ;
        // Thread.interrupted() 如果没有被中断 那么是false 如果显示的执行了interrupt 方法就会修改为 true
        while(!Thread.interrupted())
            System.out.println(this.getName() + " " + i);
            i++;
        

        System.out.println(this.getName()+ " end .... ");

    


利用InterruptedException: 如果线程因为执行join(),sleep()或者wait()而进入阻塞状态,此时要想停止它,可以让他调用interrupt(),程序会抛出InterruptedException异常。我们利用这个异常可以来终止线程。

package com.bobo;

public class FunDemo08 extends Thread

    public static void main(String[] args) throws InterruptedException 
        Thread t1 = new FunDemo08();
        t1.start();
        Thread.sleep(3000);
         t1.interrupt(); // 中断线程 将中断标志由false修改为了true
        // t1.stop(); // 直接就把线程给kill掉了
        System.out.println("main .... ");
    

    @Override
    public void run() 
        System.out.println(this.getName() + " start...");
        int i = 0 ;
        // Thread.interrupted() 如果没有被中断 那么是false 如果显示的执行了interrupt 方法就会修改为 true
         while(!Thread.interrupted())
        //while(!Thread.currentThread().isInterrupted())
             try 
                 Thread.sleep(10000);
              catch (InterruptedException e) 
                 e.printStackTrace();
				 break;
             
             System.out.println(this.getName() + " " + i);
            i++;
        

        System.out.println(this.getName()+ " end .... ");

    


4.介绍下线程中的常用方法

1.start方法

start方法是我们开启一个新的线程的方法,但是并不是直接开启,而是告诉CPU我已经准备好了,快点运行我,这是启动一个线程的唯一入口。

void	start()
// 导致此线程开始执行; Java虚拟机调用此线程的run方法。

2.run方法

线程的线程体,当一个线程开始运行后,执行的就是run方法里面的代码,我们不能直接通过线程对象来调用run方法。因为这并没有产生一个新的线程。仅仅只是一个普通对象的方法调用。

void	run()
// 如果这个线程使用单独的Runnable运行对象构造,则调用该Runnable对象的run方法; 否则,此方法不执行任何操作并返回。

3.getName方法

获取线程名称的方法

String	getName()
返回此线程的名称。

4.优先级

我们创建的多个线程的执行顺序是由CPU决定的。Java中提供了一个线程调度器来监控程序中启动后进入就绪状态的所有的线程,优先级高的线程会获取到比较多

运行机会

    /**
     * 最小的优先级是 1
     */
    public final static int MIN_PRIORITY = 1;

   /**
     * 默认的优先级都是5
     */
    public final static int NORM_PRIORITY = 5;

    /**
     * 最大的优先级是10
     */
    public final static int MAX_PRIORITY = 10;

大家会发现,设置了优先级后输出的结果和我们预期的并不一样,这是为什么呢?优先级在CPU调动线程执行的时候会是一个参考因数,但不是决定因数,

5.sleep方法

将当前线程暂定指定的时间,

static void	sleep(long millis)
// 使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。

6.isAlive

获取线程的状态。

package com.bobo.fundemo;

public class FunDemo04 

    /**
     * isAlive方法
     * @param args
     */
    public static void main(String[] args) 

        System.out.println("main  start ...");
        Thread t1 = new Thread(new Runnable() 
            @Override
            public void run() 
                System.out.println(Thread.currentThread().getName() + " .... ");
                try 
                    Thread.sleep(100);
                 catch (InterruptedException e) 
                    e.printStackTrace();
                
            
        );
        System.out.println("线程的状态:"+t1.isAlive());
        t1.start();
        System.out.println("线程的状态:"+t1.isAlive());
        System.out.println("main  end ...");
    


输出结果

main  start ...
线程的状态:false
线程的状态:true
main  end ...
Thread-0 .... 

7.join

调用某线程的该方法,将当前线程和该线程合并,即等待该线程结束,在恢复当前线程的运行

package com.bobo.fundemo;

public class FunDemo05 

    /**
     * 线程的合并
     *     join方法
     * @param args
     */
    public static void main(String[] args) 
        System.out.println("main  start ...");
        Thread t1 = new Thread(new Runnable() 
            @Override
            public void run() 
                for(int i = 0 ; i < 10; i++)
                    System.out.println(Thread.currentThread().getName() + " 子线程执行了...");
                    try 
                        Thread.sleep(100);
                     catch (InterruptedException e) 
                        e.printStackTrace();
                    
                

            
        );
        t1.start();
        try 
            t1.join(); // 线程的合并,和主线程合并  相当于我们直接调用了run方法
         catch (InterruptedException e) 
            e.printStackTrace();
        
        System.out.println("main end ...");
    


输出结果:

main  start ...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
main end ...

8.yield

让出CPU,当前线程进入就绪状态

package com.bobo.fundemo;

public class FuneDemo06 extends Thread

    public FuneDemo06(String threadName)
        super(threadName);
    

    /**
     * yield方法  礼让
     *
     * @param args
     */
    public static void main(String[] args) 
        FuneDemo06 f1 = new FuneDemo06("A1");
        FuneDemo06 f2 = new FuneDemo06("A2");
        FuneDemo06 f3 = new FuneDemo06("A3");

        f1.start();
        f2.start();
        f3.start();
    

    @Override
    public void run() 
        for(int i = 0 ; i < 100; i ++)
            try 
                Thread.sleep(10);
             catch (InterruptedException e) 
                e.printStackTrace();
            

            if(i%10 == 0 && i != 0)
                System.out.println(Thread.currentThread().getName()+" 礼让:" + i);
                Thread.currentThread(以上是关于2023-JavaSE最新整理面试题-IO和多线程专题的主要内容,如果未能解决你的问题,请参考以下文章

最全Linux面试题(2020最新版)

2020 年版最新最全的 Linux 面试题!

600+ 道 Java面试题及答案整理(2021最新版)

600+ 道 Java面试题及答案整理(2021最新版)

IOS面试题(多线程) --- 锁

细节决定成败!熬夜整理最新大厂Java高频面试题,面试经历分享