"Java面试题,涵盖进程与线程的区别、TreeMap的定义、线程停止、多线程编程最佳实践、Java编码方案及其特点、唤醒阻塞线程的方法、内存区域的解释以及多线程同步策略和自旋锁的概念。"
### 1. 进程与线程的区别
**进程**是一个在内存中运行的应用程序,每个程序运行时都会成为一个独立的进程,拥有自己的资源,如代码、数据和系统资源。**线程**是进程内部的执行单元,它共享进程的资源,拥有独立的程序计数器、栈和局部变量。进程间的资源是独立的,而线程间可以共享数据。进程切换代价较高,线程切换则相对较低。
### 2. TreeMap的定义
TreeMap是Java集合框架中的一种有序映射(Map)实现,它基于红黑树数据结构。TreeMap保证了插入的键值对按照其键的自然顺序(或提供的比较器)进行排序。这使得TreeMap可以提供有序的遍历,同时支持高效的查找、插入和删除操作。
### 3. 如何停止一个正在运行的线程
在Java中,无法直接停止一个线程。通常建议通过设置标志位来通知线程停止工作,然后在线程的run()方法中检查这个标志位,当标志位为true时,线程会自行结束。使用`Thread.interrupt()`可以中断线程,但并不保证线程会立即停止,需要在代码中处理InterruptedException。
### 4. 多线程编程的最佳实践
- 使用`ExecutorService`管理线程池,避免线程创建和销毁的开销。
- 尽量避免长时间阻塞操作,如I/O操作。
- 合理使用同步机制,如`synchronized`关键字、`ReentrantLock`等,防止竞态条件。
- 使用原子类(`java.util.concurrent.atomic`包)减少锁的使用。
- 避免死锁、活锁和饥饿现象。
- 实现`Runnable`接口而不是继承`Thread`类,除非需要直接继承其他类。
### 5. Java语言编码方案及特点
Java语言默认采用UTF-8编码方案,它是Unicode字符集的一个变体,具有广泛的字符支持,能够处理多种语言的字符。UTF-8的特点包括:
- 变长编码,节省空间,对英文字符效率高。
- 兼容ASCII码。
- 不同长度的字节序列代表不同的字符,有助于错误检测。
### 6. 唤醒一个阻塞的线程
在Java中,可以使用`Object`类的`notify()`或`notifyAll()`方法唤醒等待在该对象监视器上的线程。但需要注意,被唤醒的线程并不会立即执行,而是进入就绪状态,等待CPU的下一个调度。
### 7. 内存中的栈、堆和方法区的用法
- **栈**:用于存储基本类型和对象引用,以及方法调用时的局部变量。栈内存的分配和释放非常快,但大小有限。
- **堆**:存放所有实例对象和数组。堆内存的大小可动态扩展,并且线程间共享。
- **方法区**(在Java 8之后被元空间取代):存储类信息、常量、静态变量和编译后的代码。同样为所有线程共享。
### 8. 多线程同步方法
- `synchronized` 关键字:用于方法或代码块,确保同一时间只有一个线程执行特定代码。
- `Lock` 接口和实现类(如`ReentrantLock`):提供了比`synchronized`更细粒度的锁控制。
- `volatile` 关键字:确保多线程环境下的可见性和有序性,但不保证原子性。
- `wait()`, `notify()` 和 `notifyAll()`:用于对象级别的同步,线程通过这些方法等待或唤醒其他线程。
- `CyclicBarrier` 和 `Semaphore`:提供同步和计数器功能,用于协调多个线程的执行。
### 9. 自旋锁
自旋锁是一种非阻塞的同步机制。当线程尝试获取一个被其他线程占用的锁时,自旋锁会让线程循环等待(即“自旋”),直到锁变为可用状态。自旋锁适用于持有锁的时间很短的情况,因为它避免了线程上下文切换的开销,但如果持有锁的时间较长,自旋会消耗大量CPU资源。在Java中,`java.util.concurrent.locks.LockSupport.parkNanos()` 和 `LockSupport.unpark(Thread)` 可用于实现自旋锁。