12、Thread类的start()和run()方法是什么?
start():简单来说,start() 方法用于启动或开始执行新创建的线程。当调用 start() 方法时,会创建一个新线程,并且这个新创建的线程会执行保存在 run() 方法中的任务。一个人只能调用一次 start() 方法。
run():简单来说,run() 方法用于启动或开始执行同一个线程。当调用 run() 方法时,不会像 start() 方法那样创建新线程。该方法由当前线程执行。可以多次调用 run() 方法。
13.线程池?
线程池只是启动时预初始化或工作线程的集合,可用于执行任务并在完成时放回池中。它被称为池线程,其中创建了一组固定大小的线程。通过减少应用程序线程的数量并管理它们的生命周期,可以使用线程池来缓解性能问题。使用线程,可以提高性能并实现更好的系统稳定性。为了创建线程池,java.util.concurrent.Executors 类通常提供工厂方法。
14. join() 方法的目的是什么?
join()方法通常用于暂停当前线程的执行,除非并且直到调用 join 的指定线程死亡或完成。要停止一个线程运行直到另一个线程结束,可以使用此方法。它将一个线程执行的开始连接到另一个线程执行的结束。它被认为是线程类的最终方法。
15. 垃圾回收是什么意思?
垃圾回收基本上是一个自动管理内存的过程。它使用了几种 GC 算法,其中流行的一种包括 Mark 和 Sweep。该过程包括三个阶段,即标记、删除和压缩/复制。简单来说,垃圾收集器找到程序不再需要的对象,然后删除或移除这些未使用的对象以释放内存空间。
16. 解释死锁的含义以及何时会发生?
死锁,顾名思义,就是多个线程永远被阻塞的情况。它通常发生在多个线程持有不同资源的锁并等待其他资源完成其任务时。
上图显示了两个线程被永远阻塞的死锁情况。线程 1 持有对象 1 但需要对象 2 才能完成处理,而线程 2 持有对象 2 但首先需要对象 1。在这种情况下,它们都将永远保持锁定并且永远不会完成任务。
17.解释Java中的volatile变量?
volatile 变量基本上是一个关键字,用于确保和解决多线程编程中变量更改的可见性。此关键字不能与类和方法一起使用,而是可以与变量一起使用。它只是用于实现线程安全。如果将任何变量标记为易失性,那么所有线程都可以直接从主存而不是 CPU 缓存中读取其值,以便每个线程都可以获取变量的更新值。
18.线程之间如何通信?
线程可以使用三种方法进行通信,即wait()、notify() 和notifyAll()。handle
19、两个线程可以同时执行两种方法(静态和非静态)吗?
对的,这是可能的。如果两个线程都获得了不同对象的锁,那么它们可以同时执行而不会出现任何问题。
20. finalize() 方法的目的是什么?
Finalize() 方法基本上是 Object 类的一种方法,专门用于在垃圾回收之前对非托管资源执行清理操作。它根本不打算被称为普通方法。完成 finalize() 方法后,对象会自动销毁。
21.什么是同步过程?为什么要使用它?
同步基本上是 java 中的一个过程,它启用了一种简单的策略来避免线程干扰和内存一致性错误。此过程确保当一个线程尝试访问共享资源时,该资源一次只被一个线程使用。它可以通过以下三种不同的方式实现:
- 通过同步方法
- 通过同步块
- 通过静态同步
语法:
synchronized (object) { //statement to be synchronized }
22. 什么是同步方法和同步块?应该首选哪一个?
同步方法:在此方法中,线程在进入同步方法时获取对象上的锁,并在离开该方法时正常或通过抛出异常释放锁。除非当前线程完成执行并释放锁,否则其他线程不能使用整个方法。当想要锁定特定方法的全部功能时,可以使用它。
Synchronized Block:在该方法中,线程在synchronized关键字后面的括号内的对象上获取锁,并在离开块时释放锁。除非同步块存在,否则没有其他线程可以获取锁定对象上的锁。当一个人想要保持程序的其他部分可供其他线程访问时,可以使用它。
同步块应该更受青睐,因为它可以提高特定程序的性能。它只锁定程序的某个部分(关键部分)而不是整个方法,因此导致争用较少。
23. 什么是线程饥饿?
线程饥饿基本上是一种情况或条件,即线程无法定期访问共享资源,因此无法继续或取得进展。这是因为其他线程优先级高,占用资源时间过长。这通常发生在没有获得 CPU 以继续执行的低优先级线程上。
24. 什么是活锁?当它发生时会发生什么?
与死锁类似,活锁也是另一个并发问题。在这种情况下,线程的状态在彼此之间发生变化而没有任何进展。线程不会被阻塞,但由于资源不可用而停止执行。
25.什么是阻塞队列?
BlockingQueue 基本上代表了一个线程安全的队列。生产者线程使用 put() 方法将资源/元素插入队列,除非它已满,而消费者线程使用 take() 方法从队列中获取资源,直到它变空。但是如果一个线程试图从一个空队列中出队,那么一个特定的线程将被阻塞,直到某个其他线程将一个项目插入队列,或者如果一个线程试图将一个项目插入一个已经满的队列,那么一个特定的线程将被阻塞,直到一些线程从队列中取出一个项目。
示例:
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; public class BlockingQueuePCExample { public static void main(String[] args) { BlockingQueue<String> queue=new ArrayBlockingQueue<>(5); Producer producer=new Producer(queue); Consumer consumer=new Consumer(queue); Thread producerThread = new Thread(producer); Thread consumerThread = new Thread(consumer); producerThread.start(); consumerThread.start(); } static class Producer implements Runnable { BlockingQueue<String> queue=null; public Producer(BlockingQueue queue) { super(); this.queue = queue; } @Override public void run() { try { System.out.println("Producing element 1"); queue.put("Element 1"); Thread.sleep(1000); System.out.println("Producing element 2"); queue.put("Element 2"); Thread.sleep(1000); System.out.println("Producing element 3"); queue.put("Element 3"); } catch (InterruptedException e) { e.printStackTrace(); } } } static class Consumer implements Runnable { BlockingQueue<String> queue=null; public Consumer(BlockingQueue queue) { super(); this.queue = queue; } @Override public void run() { while(true) { try { System.out.println("Consumed "+queue.take()); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
输出:
Producing element 1 Consumed Element 1 Producing element 2 Consumed Element 2 Producing element 3 Consumed Element 3
26.一个线程可以启动两次吗?
不,一旦线程启动并完成其执行,就根本不可能重新启动线程。线程只运行一次,如果您尝试第二次运行它,那么它将抛出运行时异常,即 java.lang.IllegalThreadStateException。
示例:
public class TestThreadTwice1 extends Thread{ public void run(){ System.out.println(" thread is executing now........"); } public static void main(String args[]){ TestThreadTwice1 t1=new TestThreadTwice1(); t1.start(); t1.start(); } }
输出:
thread is executing now........ Exception in thread "main" java.lang.IllegalThreadStateException
27.解释上下文切换?
上下文切换基本上是多线程的一个重要特性。它被称为 CPU 从一个线程或进程切换到另一个线程或进程。它允许多个进程共享同一个 CPU。在上下文切换中,存储线程或进程的状态,以便稍后可以在需要时恢复线程的执行。
28. 什么是 CyclicBarrier 和 CountDownLatch?
CyclicBarrier 和 CountDownLatch 都是管理多线程编程所必需的。但是它们之间有一些区别,如下所示:
CyclicBarrier:它是一种使用某种算法同步线程处理的工具。它使一组线程相互等待,直到它们到达共同的执行点或共同的障碍点,然后让它们进一步继续执行。即使通过设置屏障破坏了屏障,也可以重复使用相同的 CyclicBarrier。
CountDownLatch:它是一个工具,它可以让主线程等待,直到强制操作被其他线程执行并完成。简而言之,它确保一个线程在开始执行之前等待另一个线程中的执行完成。一旦计数达到 0,就不能重用相同的 CountDownLatch。
29. 线程间通信是什么意思?
线程间通信,顾名思义,是一种进程或机制,多个线程可以使用该进程或机制相互通信。它特别用于避免 java 中的线程轮询,可以使用 wait()、notify() 和 notifyAll() 方法获得。
30. 什么是线程调度器和时间片?
线程调度器:它是 JVM 的一个组件,用于在多个线程等待获得执行机会的情况下决定下一个线程将执行。通过查看分配给每个 READY 线程的优先级,线程调度程序选择下一个运行来执行。线程调度主要使用两种机制:抢占式调度和时间片调度。
Time Slicing:它特别用于划分CPU时间并将它们分配给活动线程。在这种情况下,每个线程将获得一个预定义的时间片来执行。当时间到期时,特定线程必须等到其他线程有机会以循环方式使用它们的时间。每个正在运行的线程都会在固定的时间段内执行。
31.什么是关机钩子?
关闭挂钩只是在 JVM 关闭之前隐式调用的线程。它是 JVM 最重要的特性之一,因为它提供了进行资源清理或保存 JVM 关闭的应用程序状态的能力。通过调用 Runtime 类的 halt(int) 方法,可以停止关闭钩子。使用以下方法,可以添加关闭挂钩。
public void addShutdownHook(Thread hook){} Runtime r=Runtime.getRuntime(); r.addShutdownHook(new MyThread());
32.什么是Busy SPinning?
Busy Spinning,也称为Busy-waiting,是一种线程等待某些条件发生的技术,而不调用等待或睡眠方法并释放CPU。在这种情况下,可以通过让它运行一个空循环一段时间来暂停一个线程,它甚至不给 CPY 控制权。因此,它用于保留 CPU 缓存并避免重建缓存的成本。
33、什么是ConcurrentHashMap和Hashtable?在java中,为什么ConcurrentHashMap被认为比Hashtable快?
ConcurrentHashMap:它是在 Java 1.5 中引入的,用于使用多个存储桶存储数据。顾名思义,它允许对地图进行并发读写操作。它只在进行迭代时锁定映射的特定部分以提供线程安全,以便其他读取器仍然可以访问映射而无需等待迭代完成。
Hashtable:它是一个线程安全的遗留类,在旧版本的 java 中引入,用于使用哈希表存储键或值对。与 ConcurrentHashMap 不同,它不提供任何无锁读取。它只是在进行迭代时锁定整个地图。
ConcurrentHashMap 和 Hashtable 都是线程安全的,但与 Hashtable 不同,ConcurrentHashMap 通常会避免读锁并提高性能。与 Hashtable 不同,ConcurrentHashMap 还提供无锁读取。因此,ConcurrentHashMap 被认为比 Hashtable 更快,尤其是当读取器的数量比写入器的数量多时。
34.解决线程优先级
线程优先级仅仅意味着具有最高优先级的线程将在低优先级线程之前获得执行的机会。可以指定优先级,但最高优先级线程不必在低优先级线程之前执行。线程调度程序根据线程优先级将处理器分配给线程。优先级范围从最低优先级到最高优先级在 1-10 之间变化。
35. Java中的ThreadLocal变量是什么意思?
ThreadLocal 变量是由 Java ThreadLocal 类创建和提供的特殊类型的变量。这些变量只允许被同一个线程读写。两个线程无法看到彼此的 ThreadLocal 变量,因此即使它们执行相同的代码,也不会出现任何竞争条件,并且代码将是线程安全的。
示例:
public class ThreadLocalExp { public static class MyRunnable implements Runnable { private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(); @Override public void run() { threadLocal.set( (int) (Math.random() * 50D) ); try { Thread.sleep(1000); } catch (InterruptedException e) { } System.out.println(threadLocal.get()); } } public static void main(String[] args) { MyRunnable runnableInstance = new MyRunnable(); Thread t1 = new Thread(runnableInstance); Thread t2 = new Thread(runnableInstance); // this will call run() method t1.start(); t2.start(); } }
输出:
10 33 10 33
36.什么是信号量?
信号量被认为是一种线程同步结构,通常需要使用计数器来控制和管理对共享资源的访问。它只是设置线程的限制。信号量类在包 java.util.concurrent 中定义,可用于在线程之间发送信号以避免丢失信号或保护临界区。它还可以用于实现资源池或有界集合。
37. 解释线程组。为什么我们不应该使用它?
ThreadGroup 是一个用于在单个对象中创建多组线程的类。这组线程以三种结构的形式存在,其中每个线程组都有一个父线程,但初始线程除外。线程组也可以包含其他线程组。一个线程只被允许访问关于它自己的线程组的信息,而不是其他线程组。
以前在旧版本的 Java 中,唯一没有线程组就无法工作的功能是 uncaughtException(Thread t, Throwable e)。但是现在在 Java 5 版本中,有了 Thread.setUncaughtExceptionHandler(UncaughtExceptionHandler)。所以现在即使没有线程组也可以工作,因此不需要使用线程组。
t1.setUncaughtExceptionHandler(new UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { System.out.println("exception occured:"+e.getMessage()); } };
38、什么是ExecutorService接口?
ExecutorService 接口基本上是 Executor 接口的子接口,具有一些额外的方法或特性,有助于管理和控制线程的执行。它使我们能够在线程上异步执行任务。
例子:
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; public class TestThread { public static void main(final String[] arguments) throws InterruptedException { ExecutorService e = Executors.newSingleThreadExecutor(); try { e.submit(new Thread()); System.out.println("Shutdown executor"); e.shutdown(); e.awaitTermination(5, TimeUnit.SECONDS); } catch (InterruptedException ex) { System.err.println("tasks interrupted"); } finally { if (!e.isTerminated()) { System.err.println("cancel non-finished tasks"); } e.shutdownNow(); System.out.println("shutdown finished"); } } static class Task implements Runnable { public void run() { try { Long duration = (long) (Math.random() * 20); System.out.println("Running Task!"); TimeUnit.SECONDS.sleep(duration); } catch (InterruptedException ex) { ex.printStackTrace(); } } } }
输出:
Shutdown executor shutdown finished
39、如果我们不重写线程类的run()方法会发生什么?
如果我们不覆盖 run() 方法,什么都不会发生。编译器不会显示任何错误。它将执行线程类的 run() 方法,我们不会得到任何输出,因为 run() 方法的实现是空的。
**例子:**
class MyThread extends Thread { //don't override run() method } public class DontOverrideRun { public static void main(String[] args) { System.out.println("Started Main."); MyThread thread1=new MyThread(); thread1.start(); System.out.println("Ended Main."); } }
输出:
Started Main. Ended Main.
40.什么是锁接口?为什么使用锁接口比使用同步块更好?
Lock 接口是在 Java 1.5 中引入的,一般用作同步机制,为阻塞提供重要的操作。
使用 Lock 接口优于 Synchronization 块的优点:
- Lock 接口的方法,即 Lock() 和 Unlock() 可以在不同的方法中调用。这是锁接口相对于同步块的主要优势,因为同步块完全包含在单个方法中。
- 与同步块不同,锁接口更灵活,确保等待时间最长的线程获得公平的执行机会。
41、是否可以直接调用run()方法启动一个新线程?
不,这根本不可能。您需要调用 start 方法来创建新线程,否则 run 方法不会创建新线程。相反,它将在当前线程中执行。
42. 在多线程编程中,每个线程都可以有自己的栈吗?
当然,这是可能的。在多线程编程中,每个线程在内存中维护自己独立的堆栈区域,因此每个线程彼此独立而不是相互依赖。
43. 结论
总的来说,多线程是 Java 和Android开发的一个非常重要的部分。这对于提高程序的效率非常有帮助,同时也减少了存储资源的使用。在本文中,多线程相关的重要面试问题以及面试中最常被问到的答案,希望可以让你解惑或者引入们。拿到心仪的offer。