1.进程和线程
1.我们对“并发”一词有什么了解?
并发是程序同时执行多个计算的能力。这可以通过将计算分布在计算机的可用CPU内核上,甚至在同一网络内的不同计算机上来实现。
2.进程和线程之间有什么区别?
进程是操作系统提供的执行环境,它具有自己的一组私有资源(例如,内存,打开的文件等)。与流程相反,线程位于流程内,并与流程的其他线程共享资源(内存,打开的文件等)。在不同线程之间共享资源的能力使线程更适合于对性能有重要要求的任务。
3.在Java中,什么是进程和线程?
在Java中,进程对应于正在运行的Java虚拟机(JVM),而线程位于JVM中,并且可以由Java应用程序在运行时动态创建和停止。
4.什么是调度程序?
调度程序是一种调度算法的实现,该算法管理进程和线程对某些有限资源(如处理器或某些I / O通道)的访问。大多数调度算法的目标是为可用的进程/线程提供某种负载平衡,以确保每个进程/线程都有适当的时间范围以排他地访问所请求的资源。
5. Java程序至少具有多少个线程?
每个Java程序都在主线程中执行;因此,每个Java应用程序都有至少一个线程。
6. Java应用程序如何访问当前线程?
可以通过调用currentThread()
JDK类的静态方法来访问当前线程java.lang.Thread
:
public class MainThread { public static void main(String[] args) { long id = Thread.currentThread().getId(); String name = Thread.currentThread().getName(); .. } }
7.每个Java线程都有哪些属性?
每个Java线程都具有以下属性:
- 在JVM中唯一的long类型的标识符
- 类型为String的名称
- int类型的优先级
- 类型状态
java.lang.Thread.State
- 线程所属的线程组
8.线程组的目的是什么?
每个线程都属于一组线程。JDK类java.lang.ThreadGroup
提供了一些方法来处理整个线程组。使用这些方法,例如,我们可以中断组中的所有线程或设置其最大优先级。
9.线程可以具有哪些状态,每个状态的含义是什么?
NEW:
尚未启动的线程处于此状态。RUNNABLE:
在Java虚拟机中执行的线程处于这种状态。BLOCKED:
等待监视器锁定而被阻塞的线程处于此状态。WAITING:
无限期地等待另一个线程执行特定操作的线程处于此状态。TIMED_WAITING:
正在等待另一个线程执行操作的线程最多达到指定的等待时间,该线程处于此状态。TERMINATED:
退出的线程处于此状态。
10.多线程编程的好处是什么?
多线程代码可以在以下方面提供帮助:
- 改善应用程序响应能力
- 有效地使用多处理器
- 改善计划结构
- 使用更少的系统资源
11.在多线程环境中遇到哪些常见问题?
- 僵局–两个线程A和B分别持有lock_A和lock_B。他们两个都希望访问资源R。为了安全地访问R,需要lock_A和lock_B。但是线程A需要lock_B,线程B需要lock_A。但是他们两个都不准备放弃他们持有的锁。因此,没有任何进展。陷入僵局!
- 种族条件–考虑生产者-消费者的经典例子。如果您在添加或从队列中删除项目之前忘记锁定该怎么办?想象一下,两个线程A和B试图在不锁定的情况下添加项目。线程A访问队列的后面。然后,调度程序就有机会运行线程B,该线程B成功添加了项并更新了尾指针。现在,线程A读取的尾指针已过时,但它认为它是尾并添加了该项。因此,B添加的项目是LOST!数据结构已损坏!更糟糕的是,清理时还可能导致内存泄漏。
- 数据竞争–想象应该设置的标志变量。假设您已锁定以避免比赛条件。现在,不同的线程想要设置不同的值。由于调度程序可以以任何方式调度线程执行,因此您最终不知道标志的值。
- 饥饿–这是线程调度程序引起的问题。一些线程没有机会运行和完成,或者无法获得所需的锁,因为其他线程被赋予了更高的优先级。他们“饿死”了CPU周期或其他资源。
- 优先级反转–想象两个线程A和B。A具有比B高的优先级,因此比B具有更多的CPU周期。但是在访问共享资源时,B持有A所需的锁并屈服。现在,如果没有锁,A无法做任何事情,并且浪费了大量CPU周期,因为B没有获得足够的周期,但是拥有了锁。
12.我们如何设置线程的优先级?
使用方法可以设置线程的优先级setPriority(int)
。要将优先级设置为最大值,我们使用常量Thread.MAX_PRIORITY
,将其设置为最小值,我们使用常量,Thread.MIN_PRIORITY
因为这些值在不同的JVM实现之间可能会有所不同。
13.什么是多线程中的上下文切换?
纯粹的多任务处理不存在。同时执行两项具有挑战性的任务是不可能的。因此,当我们执行多任务时,我们真正要做的就是不断地从一项任务切换到另一项任务。这就是上下文切换。
该术语起源于计算机科学。但是,它同样适用于人类执行的心理任务。毕竟,人类的思想在许多方面都类似于CPU。
就像运行多线程进程的CPU在运行另一个线程时暂时搁置一个给定线程的执行一样,人类的大脑也搁置一个任务以将其重点转移到另一个任务上。
14. Java中的绿色线程和本地线程之间的区别?
- 绿色线程是指Java虚拟机本身在一个操作系统进程中创建,管理和上下文切换所有Java线程的模型。没有使用操作系统线程库。
- 本机线程是指Java虚拟机使用操作系统线程库(在UnixWare上名为libthread)创建和管理Java线程,并且每个Java线程都映射到一个线程库线程。
15.我们对种族条件一词有什么了解?
竞争条件描述的星座图,其中某些多线程实现的结果取决于参与线程的确切计时行为。在大多数情况下,具有这种行为是不希望的,因此,竞赛条件一词还意味着由于缺少线程同步而导致的错误会导致不同的结果。竞争条件的一个简单示例是两个并行线程对整数变量的递增。由于该操作由一个以上的单个原子操作组成,因此可能发生两个线程读取并增加相同值的情况。在此并发增量之后,整数变量的数量不会增加2,而只会增加1
16.将对象实例从一个线程传递到另一个线程时,您需要考虑什么?
在线程之间传递对象时,必须注意这些对象不能同时由两个线程操纵。一个示例是一个Map
实现,其键/值对由两个并发线程修改。为了避免并发修改出现问题,可以将对象设计为不可变的。
17.是否可以通过使用多线程来提高应用程序的性能?列举一些例子。
如果我们有多个CPU内核可用,并且可以在可用的CPU内核上并行化计算,则可以通过多线程来提高应用程序的性能。一个示例是应该缩放存储在本地目录结构中的所有图像的应用程序。生产者/消费者实现可以使用一个线程扫描目录结构和执行实际缩放操作的一堆工作线程,而不是一个接一个地遍历所有图像。另一个示例是镜像某些网页的应用程序。生产者线程可以解析第一个HTML页面并将发现的链接发布到队列中,而不是先加载一个HTML页面。工作线程监视队列并加载解析器找到的网页。
18.我们对可扩展性一词有什么了解?
可伸缩性是指程序通过向其添加更多资源来提高性能的能力。
19.是否可以通过使用多个处理器来计算应用程序的理论最大速度?
通过为应用程序提供多个处理器,阿姆达尔定律提供了一个计算理论上最大速度的公式。理论上的加速由S(n) = 1 / (B + (1-B)/n)
下式计算:其中n
表示处理器数量和B
无法并行执行的程序部分。当n收敛于无穷大时,项(1-B)/n
收敛于零。因此,在这种特殊情况下,公式可以简化为1/B
。正如我们所看到的,理论上最大加速比是必须依次执行的分数的倒数。这意味着该分数越低,可以实现越多的理论加速。
20.提供一个示例,说明为什么单线程应用程序的性能改进会导致多线程应用程序的性能下降。
此类优化的一个突出示例是将List
元素数量保持为单独变量的实现。由于该size()
操作不必遍历所有元素,而是可以直接返回当前数量的元素,因此可以提高单线程应用程序的性能。在多线程应用程序中,由于多个并发线程可能会将元素插入列表,因此必须通过锁来保护其他计数器。当列表的更新次数超过size()
操作的调用次数时,此附加锁定可能会降低性能。
2.线程对象
2.1定义和启动线程
21.如何用Java创建线程?
基本上,有两种方法可以用Java创建线程。
第一个是编写一个扩展JDK类java.lang.Thread
并调用其方法的类start()
:
public class MyThread extends Thread { public MyThread(String name) { super(name); } @Override public void run() { System.out.println("Executing thread "+Thread.currentThread().getName()); } public static void main(String[] args) throws InterruptedException { MyThread myThread = new MyThread("myThread"); myThread.start(); } }
第二种方法是实现接口java.lang.Runnable
,并将此实现作为参数传递给的构造函数java.lang.Thread
:
public class MyRunnable implements Runnable { public void run() { System.out.println("Executing thread "+Thread.currentThread().getName()); } public static void main(String[] args) throws InterruptedException { Thread myThread = new Thread(new MyRunnable(), "myRunnable"); myThread.start(); } }
22.为什么不应该通过调用其方法来停止线程stop()
?
线程不应该被使用过时的方法来停止stop()
的java.lang.Thread
,因为该方法的调用导致线程解锁已获取所有的显示器。如果受释放锁之一保护的任何对象处于不一致状态,则此状态对所有其他线程可见。当其他线程对此不一致的对象进行处理时,这可能导致任意行为。
23.是否可以启动一个线程两次?
否,在通过调用线程的start()
方法启动线程之后,第二次调用start()
将抛出IllegalThreadStateException
。
24.以下代码的输出是什么?
public class MultiThreading { private static class MyThread extends Thread { public MyThread(String name) { super(name); } @Override public void run() { System.out.println(Thread.currentThread().getName()); } } public static void main(String[] args) { MyThread myThread = new MyThread("myThread"); myThread.run(); } }
上面的代码产生输出“ main”而不是“ myThread”。从该main()
方法的第二行可以看出,我们错误地调用run()
而不是方法start()
。因此,没有启动新线程,但是该方法run()
在主线程中执行。
25.什么是守护线程?
守护程序线程是当JVM决定是否应该停止时,不评估其执行状态的线程。当所有用户线程(与守护程序线程相反)终止时,JVM停止。因此,一旦所有用户线程停止,守护程序线程就可以用于实现监视功能,例如,JVM停止了该线程:
public class Example { private static class MyDaemonThread extends Thread { public MyDaemonThread() { setDaemon(true); } @Override public void run() { while (true) { try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) throws InterruptedException { Thread thread = new MyDaemonThread(); thread.start(); } }
即使守护线程仍在其无尽的while循环中运行,以上示例应用程序也会终止。
26.什么是Java线程转储?
Java线程转储是一种找出JVM中每个线程在特定时间点正在做什么的方法。
如果您的Java应用程序有时在负载下运行时挂起,这将特别有用,因为对转储的分析将显示线程卡在哪里。
您可以在Unix / Linux下通过运行kill -QUIT <pid>来生成线程转储,而在Windows下则可以通过按Ctl + Break来生成线程转储。
27.是否可以在启动普通用户线程后将其转换为守护线程?
用户线程一旦启动就无法转换为守护线程。thread.setDaemon(true)
在已经运行的线程实例上调用该方法会导致IllegalThreadStateException
。
28.忙碌等待使我们了解什么?
繁忙等待是指通过执行一些活动计算来等待事件的实现,这些计算使线程/进程占用了处理器,尽管调度程序可以将其从处理器中删除。繁忙等待的一个示例是将等待时间花费在一个循环中,该循环一次又一次地确定当前时间,直到到达某个时间点为止:
Thread thread = new Thread(new Runnable() { @Override public void run() { long millisToStop = System.currentTimeMillis() + 5000; long currentTimeMillis = System.currentTimeMillis(); while (millisToStop > currentTimeMillis) { currentTimeMillis = System.currentTimeMillis(); } } });