java多线程面试——旧版api

在Android开发的面试中,Java多线程的问题是绕不开的。这个系列主要介绍面试过程中涉及到的多线程的知识点,以及相关的面试题。这是本系列的第二篇,介绍Java中多线程的旧版api,及其对应的常见的面试题。

java创建线程的方式

java创建线程有三种方式,分别是:

  1. 通过Runnable创建线程
java 复制代码
public class RunnableTest implements Runnable {
    @Override
    public void run() {
        
    }
}

public static void main(String[] args) throws Exception {
   Runnable runnable = new RunnableTest();
   Thread thread = new Thread(runnable);
   thread.start();//启动线程
}

2.继承Thread创建线程

java 复制代码
public class MyThread extends Thread {
    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
      
    }
}

public static void main(String[] args) throws Exception {
  Thread thread = new MyThread("线程");
  thread.start();
}
  1. 使用Callable和Future创建线程
arduino 复制代码
复制代码
public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        
    }
}

public static void main(String[] args) throws Exception {
   Callable<String> callable = new MyCallable();
   FutureTask<String> task = new FutureTask<>(callable);
   Thread t = new Thread(task);
   t.start();
   System.out.println(task.get());
}

面试题1:一个线程两次调用start()方法会出现什么情况

Java的线程是不允许启动两次的,第二次调用会抛出 IllegalThreadStateException 异常。

面试题2:runnable 和 callable 有什么区别?

runnable 没有返回值,callable 可以拿到有返回值,callable 可以看作是 runnable 的补充。

面试题3: 线程的 run() 和 start() 有什么区别?

start() 方法用于启动线程,run() 方法用于执行线程的运行时代码。run() 可以重复调用,而 start() 只能调用一次。

线程的生命周期

线程有六种状态,分别如下:

  1. NEW(初始化状态)
  2. RUNNABLE(可运行 / 运行状态)
  3. BLOCKED(阻塞状态)
  4. WAITING(无时限等待)
  5. TIMED_WAITING(有时限等待)
  6. TERMINATED(终止状态)

可以通过getState()获取线程所处的状态。状态之间的切换原因如下:

状态 切换原因
NEW(初始化状态) 创建Thread
RUNNABLE(可运行 / 运行状态) 调用start方法
BLOCKED(阻塞状态) 只有线程等待synchronized锁时
WAITING(无时限等待) 调用wait、join、LockSupport.park 时
TIMED_WAITING(有时限等待) 调用sleep,带时间的wait、join、LockSupport.parkNanos
TERMINATED(终止状态) 执行完run方法后,自动切换到TERMINATED状态

面试题4:如果当前线程等待 CPU 使用权或者等待 I/O ,这时线程的状态是什么?

线程等待 CPU 使用权与等待 I/O 时也是RUNNABLE状态

wait、notify、notifyAll

java 复制代码
synchronized (obj) {  
    try {  
        obj.wait();  
    } catch (InterruptedException e) {  
        throw new RuntimeException(e);  
    }
}

调用 wait 方法会让线程进入等待状态(WAITING),同时释放掉锁。如果需要唤醒线程,需要调用 notify 或者 notifyAll 方法。notify 方法是随机唤醒一个线程,而 notifyAll 方法是会唤醒所有等待线程

sleep

java 复制代码
try {  
    Thread.sleep(1000);  
} catch (InterruptedException e) {  
    throw new RuntimeException(e);  
}

调用sleep方法会让线程休眠一段时间,进入有时限等待状态(TIMED_WAITING)。需要注意,sleep 方法不会释放锁,但会释放CPU资源

面试题5:wait与sleep区别有哪些

  1. wait释放锁,sleep不释放锁
  2. wait需要被唤醒,sleep不需要
  3. wait需要获取到监视器(在synchronized代码块里面),否则抛异常,sleep不需要
  4. wait是object顶级父类的方法,sleep则是Thread的方法

join

java 复制代码
//主线程执行
Thread thread = new Thread(runnable);  
thread.start();  
try {  
    thread.join();  
} catch (InterruptedException e) {  
    throw new RuntimeException(e);  
}

join 方法的作用是等待指定的线程的结束。如上面的代码所示,主线程会等待 thread 线程执行结束后再执行。这时主线程会变成 WAITING 状态。需要注意,join方法只有在start方法之后调用才有效。

线程的中断

java 复制代码
void interrupt() //请求线程中断,线程不一定会中断
boolean isInterrupted() //检测当前线程是否中断,不会改变中断状态,能多次判断
static boolean interrupted() //检测当前线程是否中断,会重置中断状态为false,只能判断一次

我们调用 interrupt 方法来请求线程中断,这时中断的操作只会改变中断状态,并不会中断程序, 这时需要我们使用 if(Thread.currentThread().isInterrupted()) 来判断是否处于中断状态,自己来处理,这样增加了灵活性。

面试题6:isInterrupted 和 interrupted 的区别

  • isInterrupted:是成员方法,作用是检测当前线程是否中断,不会改变中断状态,因此可以多次判断是否中断
  • interrupted:是静态方法,作用是检测当前线程是否中断,它会重置中断状态为false,因此只能判断一次是否中断

面试题7:中断操作在线程所有状态下都生效吗

当线程处于NewTerminated状态时,中断操作无效,即中断状态不会改变; 当线程处于RunnableBlocked时,中断操作会改变中断状态;当线程处于 WAITINGTIMED_WAITING 时,中断操作会抛出异常,isInterrupted() 不会返回 true,并且结束程序。

需要注意:当线程 A 处于 WAITING、TIMED_WAITING 状态时,如果其他线程调用线程 A 的 interrupt() 方法,会使线程 A 返回到 RUNNABLE 状态,同时线程 A 的代码会触发InterruptedException 异常。ReentranLocak.lock方法会进入waiting状态,但是不会被interrupt打断

面试题8:终止线程为什么不用 stop

stop 方法会直接杀死线程,如果此时线程持有 ReentrantLock 锁,被 stop 的线程并不会自动调用 ReentrantLock 的 unlock() 去释放锁,此时其他线程就再也没机会获得 ReentrantLock 锁。

线程优先级

每一个线程都有一个优先级,在默认的情况下,一个线程继承它父类线程的优先级。 可以使用setPriority(int newPriority)设置优先级。

优先级的等级在MIN_PRIORITY(默认值为1)和MAX_PRIORITY(默认值为10)之间;默认为 NORM_PRIORITY(默认值为5).

守护线程

java 复制代码
public static void main(String[] args) {  
    Runnable runnable = new Runnable() {  
        @Override  
        public void run() {  
            while (true);  
        }  
    };  
    Thread thread = new Thread(runnable);  
    thread.start();  
}

当我们执行上面代码的时候,由于子线程会一直执行,这时即使主线程结束了,该进程也不会结束。如果我们希望主线程结束了,子线程也会结束,我们可以通过 thread.setDaemon(true); 让子线程成为守护线程。

守护线程的唯一用途是为其他线程通过服务。注意:守护线程应该永远不去 访问固有资源,如文件,数据库,因为它会在任何时刻甚至在一个操作中间发生中断

ThreadLocal

ThreadLocal 是指线程本地变量,它可以让每个线程都有自己独立的变量副本,互不干扰。

csharp 复制代码
public class RunnableTest implements Runnable { 
    private final ThreadLocal<Integer> value = new ThreadLocal<>(); 
    
    @Override 
    public void run() {
       //设置value
       value.set(124);
       //获取value
       value.get();
    } 
 }

实现原理如下图所示:

在 Thread 类中,有 ThreadLocalMap 的属性,当我们通过 ThreadLocal 的 set 方法设置 value 时,它会往当前 Thread 中的 ThreadLocalMap 插入 key 为 ThreadLocal,value 为指定值的一项。

面试题9:为什么ThreadLocal会造成内存泄漏问题

ThreadLocal 虽然是弱引用,但是其对应的value为强引用。如果 ThreadLocal 被垃圾回收,这时 key 为 null,但是 value 不会为null。如果该线程生命周期比较长(可能是线程池的核心线程),这时value这块内存就会一直在内存中,造成内存泄漏。

synchronized

synchronized 是旧版java中来在多线程中保持同步的关键字。synchronied 可以修饰普通方法,等价于synchronized(this){},是给当前类的对象加锁;synchronied 修饰静态方法,等价于synchronized(class){},是给当前类对象加锁。

需要注意,synchronized(obj){} 中的obj不要使用 String、Integer 等的对象,这是因为它们内部会缓存常量字符串、数字,会对同步造成影响

同步容器

同步容器是早期Java解决多线程操作容器的方案,同步容器有 Hashtable、Vector、Stack。它们的实现原理很简单就是给方法加上 synchronized 关键字,但是这会造成性能的问题。新版的java中提供了新的并发容器来解决这个问题,在下一篇文章会介绍新版的多线程接口。

参考

相关推荐
笃励9 分钟前
Java面试题二
java·开发语言·python
易雪寒27 分钟前
IDEA在git提交时添加忽略文件
java·git·intellij-idea
打码人的日常分享1 小时前
企业人力资源管理,人事档案管理,绩效考核,五险一金,招聘培训,薪酬管理一体化管理系统(源码)
java·数据库·python·需求分析·规格说明书
27669582921 小时前
京东e卡滑块 分析
java·javascript·python·node.js·go·滑块·京东
爱写代码的刚子1 小时前
C++知识总结
java·开发语言·c++
冷琴19961 小时前
基于java+springboot的酒店预定网站、酒店客房管理系统
java·开发语言·spring boot
沐言人生1 小时前
Android10 Framework—Init进程-9.服务端属性值初始化
android·android studio·android jetpack
daiyang123...2 小时前
IT 行业的就业情况
java
追光天使2 小时前
【Mac】和【安卓手机】 通过有线方式实现投屏
android·macos·智能手机·投屏·有线
爬山算法2 小时前
Maven(6)如何使用Maven进行项目构建?
java·maven