Android并发编程高级面试题汇总(含详细解析 十)
  CpwfxCg9mmk0 2023年11月24日 14 0

Android并发编程高级面试题汇总最全最细面试题讲解持续更新中👊👊 👀你想要的面试题这里都有👀 👇👇👇

什么是守护线程?你是如何退出一个线程的?

这道题想考察什么?

是否了解守护线程与真实场景使用,是否熟悉线程退出该如何操作的本质区别?

考察的知识点

守护线程与线程退出的概念在项目中使用与基本知识

考生应该如何回答

守护线程

在开发过程中,直接创建的普通线程为用户线程,而另一种线程,也就是守护线程,通过setDaemon(true)将一个普通用户线程设置为守护线程。

守护线程,也叫Daemon线程,它是一种支持型、服务型线程,主要被用作程序中后台调度以及支持性工作,跟上层业务逻辑基本不挂钩。Java中垃圾回收线程就是一个典型的Daemon线程。

public class DaemonThreadTest {    
    public static void main(String[] args) {        
        // 创建线程        
        Thread daemonThread = new Thread("Daemon") {            
            @Override            
            public void run() {                
                super.run();                
                // 循环执行5次(休眠一秒,输出一句话)                
                for (int i = 0; i < 5; i++) {                    
                    try {                        
                        Thread.sleep(1000);                    
                    } catch (InterruptedException e) {                        
                        e.printStackTrace();                    
                    }                    
                    System.out.println("守护线程->" + Thread.currentThread().getName() + "正在执行");                		  }            
            }        
        };        
        // 设置为守护线程,必须在线程启动start()方法之前设置,否则会抛出IllegalThreadStateException异常  
        daemonThread.setDaemon(true);        
        // 启动守护线程        
        daemonThread.start();        
        // 这里是主线程逻辑        
        // 循环执行3次(休眠一秒,输出一句话)        
        for (int i = 0; i < 3; i++) {            
            try {                
                Thread.sleep(1000);            
            } catch (InterruptedException e) {                
                e.printStackTrace();            
            }            
            System.out.println("普通线程->" + Thread.currentThread().getName() + "正在执行");        
        }    
    }
}

在主线程里创建守护线程,然后一起执行,主线程的业务逻辑是每隔1s,输出一句log,总共循环三次,而守护线程里也是隔1s输出一句log,不过想要循环5次。其中需要注意的是设置为守护线程,必须在线程启动start()方法之前设置,否则会抛出IllegalThreadStateException异常,意思就是运行中的线程不能设置成守护线程的。最后我们运行一下main方法,看看控制台的输出结果如下:

控制台的输出普通线程->
main正在执行守护线程->
Daemon正在执行守护线程->
Daemon正在执行普通线程->
main正在执行守护线程->
Daemon正在执行普通线程->
main正在执行BUILD SUCCESSFUL in 3s2 actionable tasks: 2 executed

从上面的日志来看,两个线程都只是输出了3次,然后就执行结束,这其实就是守护线程的特点,用一句话概括就是,当守护线程所守护的线程结束时,守护线程自身也会自动关闭。

线程如何退出

要知道当Thread的run方法执行结束后,线程便会自动退出,生命周期结束,这属于线程正常退出的范畴。在实际的开发过程中,还存在另一种业务场景,因为某种原因,需要停止正在运行的线程,那该怎么办呢?

在Thread中提供了一个stop方法,stop方法是JDK提供的一个可以强制关闭线程的方法,但是不建议使用!而且Android针对JDK中的Thread进行了修改,在Android的Thread中调用stop方法会直接抛出异常:

@Deprecated
public final synchronized void stop(Throwable obj) {
	throw new UnsupportedOperationException();
}

那么当我们需要停止运行的线程时,可以通过标识位的方式实现:

使用标志位退出线程

线程执行run方法过程中,我们可以通过一个自定义变量来决定是否还需要退出线程,若满足条件,则退出线程,反之继续执行:

下面代码:注释已经加的很详细了,直接从日志的输出结果来看,很完美的停止了子线程的运行,很OK

public class StopThreadTest {    
    // 定义标志位,使用volatile,保证内存可见    
    private static volatile boolean stopFlag = false;    
    public static void main(String[] args) {        
        // 创建子线程        
        Thread thread = new Thread() {            
            @Override            
            public void run() {                
                super.run();                
                // 循环打印运行日志                
                while (!stopFlag) {                    
                    System.out.println(currentThread().getName() + " is running");                
                }                
                // 退出后,打印退出日志                
                if (stopFlag) {                    
                    System.out.println(currentThread().getName() + " is stop");                
                }            
            }        
        };        
        thread.start();        
        // 让子线程执行100ms后,将stopFlag置为true        
        try {            
            Thread.sleep(100);        
        } catch (InterruptedException e) {            
            e.printStackTrace();        
        }        
        stopFlag = true;    
    }
}
// logcat日志// Thread-0 is running// Thread-0 is running// Thread-0 is running// Thread-0 is running// Thread-0 is running// Thread-0 is running// Thread-0 is running// Thread-0 is running// Thread-0 is running// Thread-0 is running// Thread-0 is stop
使用interrupt方法中断线程

使用interrupt方法来中断线程,本质上也是标志位的方式。跟上面的原理一样,只不过是Thread类内部提供的罢了。其他线程通过调用某个线程的interrupt()方法对其进行中断操作,被中断的线程则是通过线程的isInterrupted()来进行判断是否被中断。

public class StopThreadTest {    
    public static void main(String[] args) {        
        // 创建子线程        
        Thread thread = new Thread() {            
            @Override            
            public void run() {                
                super.run();                
                // 通过isInterrupted()方法判断线程是否已经中断                
                while (!isInterrupted()) {                    
                    // 打印运行日志                    
                    System.out.println(currentThread().getName() + " is running");                
                }                
                if (isInterrupted()) {                    
                    // 打印退出日志                    
                    System.out.println(currentThread().getName() + " is stop");                
                }            
            }        
        };        
        thread.start();        
        // 让子线程执行100ms后,将stopFlag置为true        
        try {            
            Thread.sleep(100);        
        } catch (InterruptedException e) {            
            e.printStackTrace();        
        }        
        // 通过调用interrupt方法去改变标志位        
        thread.interrupt();    
    }
}

sleep 、wait、yield与join的区别,wait 的线程如何唤醒它?(字节跳动)

这道题想考察什么?

  1. 是否了解Java中线程相关的知识点
  2. 线程的生命周期

考察的知识点

  1. Java中线程的相关概念
  2. Sleep、yield、wait和jion函数的区别
  3. 多线程并发相关的知识点

考生应该如何回答

sleep 、wait、yield与join的区别

sleep、yield与join是线程方法,而wait则是Object方法:

  • sleep **,释放cpu资源,不释放锁资源,**如果线程进入sleep的话,释放cpu资源,如果外层包有Synchronize,那么此锁并没有释放掉。
  • wait,**释放cpu资源,也释放锁资源,**一般用于锁机制中 肯定是要释放掉锁的,因为notify并不会立即调起此线程,因此cpu是不会为其分配时间片的,也就是说wait 线程进入等待池,cpu不分时间片给它,锁释放掉。
  • yield:让出CPU调度,Thread类的方法,类似sleep只是不能由用户指定暂停多长时间 ,并且yield()方法只能让同优先级的线程有执行的机会。 yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。调用yield方法只是一个建议,告诉线程调度器我的工作已经做的差不多了,可以让别的相同优先级的线程使用CPU了,没有任何机制保证采纳。
  • join:一种特殊的wait,当前运行线程调用另一个线程的join方法,当前线程进入阻塞状态直到另一个线程运行结束等待该线程终止。 注意该方法也需要捕捉异常。
wait 的线程如何唤醒

执行wait方法会导致当前线程进入Blocked状态,可以调用执行wait方法的对象的notify或者notifyAll方法唤醒:

public class Main {
    public static void main(String[] args) throws InterruptedException {
        var q = new TaskQueue();
        var ts = new ArrayList<Thread>();
        for (int i=0; i<5; i++) {
            var t = new Thread() {
                public void run() {
                    // 执行task:
                    while (true) {
                        try {
                            String s = q.getTask();
                            System.out.println("execute task: " + s);
                        } catch (InterruptedException e) {
                            return;
                        }
                    }
                }
            };
            t.start();
            ts.add(t);
        }
        var add = new Thread(() -> {
            for (int i=0; i<10; i++) {
                // 放入task:
                String s = "t-" + Math.random();
                System.out.println("add task: " + s);
                q.addTask(s);
                try { Thread.sleep(100); } catch(InterruptedException e) {}
            }
        });
        add.start();
        add.join();
        Thread.sleep(100);
        for (var t : ts) {
            t.interrupt();
        }
    }
}

class TaskQueue {
    Queue<String> queue = new LinkedList<>();

    public synchronized void addTask(String s) {
        this.queue.add(s);
        this.notifyAll();
    }

    public synchronized String getTask() throws InterruptedException {
        while (queue.isEmpty()) {
            this.wait();
        }
        return queue.remove();
    }
}

在addTask()方法中调用了this.notifyAll()而不是this.notify(),使用notifyAll()将唤醒所有当前正在this锁等待的线程,而notify()只会唤醒其中一个(具体哪个依赖操作系统,有一定的随机性)。

之所以使用notifyAll,是因为可能有多个线程正在getTask()方法内部的wait()中等待,使用notifyAll()将一次性全部唤醒。通常来说,notifyAll()更安全。有些时候,如果我们的代码逻辑考虑不周,用notify()会导致只唤醒了一个线程,而其他线程可能永远处于Blocked状态。


Android并发编程高级面试题汇总(含详细解析 十)_System

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月24日 0

暂无评论

CpwfxCg9mmk0