Java多线程问题
-
进程和线程之间有什么不同?
- 进程:一个独立的运行环境,可以被看作一个程序或应用。每个进程都有自己独立的地址空间和系统资源。
- 线程:在进程中执行的一个任务。线程是进程内的一个执行单元,共享进程的资源,包括内存和文件句柄。线程比进程更轻量级,创建和切换成本更低。
-
多线程编程的好处是什么?
- 提高效率:多线程程序中,多个线程并发执行,提高了程序的效率,CPU不会因为某个线程等待资源而进入空闲状态。
- 资源共享:多个线程共享堆内存,因此创建多个线程执行任务比创建多个进程更高效。
- 响应性:多线程可以提高应用程序的响应性,例如在GUI应用中,主线程处理用户界面,其他线程处理后台任务。
-
用户线程和守护线程有什么区别?
- 用户线程:在Java程序中创建的线程,默认情况下是用户线程。用户线程会阻止JVM终止。
- 守护线程:在后台执行的线程,不会阻止JVM终止。当没有用户线程在运行时,JVM会关闭程序并退出。守护线程创建的子线程仍然是守护线程。
-
我们如何创建一个线程?
- 实现Runnable接口:创建一个实现
Runnable
接口的类,然后将其实例传递给Thread
的构造函数,创建一个Thread
对象。 - 继承Thread类:直接继承
Thread
类,重写其run
方法。
- 实现Runnable接口:创建一个实现
-
有哪些不同的线程生命周期?
- New:新创建的线程,尚未调用
start()
方法。 - Runnable:线程已调用
start()
方法,等待CPU调度。 - Running:线程正在执行。
- Blocked:线程被阻塞,等待获取锁或其他资源。
- Waiting:线程在等待另一个线程执行某个动作。
- Timed Waiting:线程在等待指定的时间后继续执行。
- Terminated:线程已执行完毕或因异常终止。
- New:新创建的线程,尚未调用
-
可以直接调用Thread类的run()方法么?
- 可以,但这样做不会启动一个新的线程,
run()
方法会像普通方法一样在当前线程中执行。为了在新的线程中执行代码,必须使用Thread.start()
方法。
- 可以,但这样做不会启动一个新的线程,
-
如何让正在运行的线程暂停一段时间?
- 使用
Thread.sleep(long millis)
方法可以让线程暂停指定的时间。需要注意的是,这不会终止线程,一旦从休眠中唤醒,线程的状态将变为Runnable
,并根据线程调度器的安排继续执行。
- 使用
-
你对线程优先级的理解是什么?
- 每个线程都有一个优先级,优先级高的线程在运行时通常会具有更高的优先权。线程优先级是一个
int
变量,范围从1到10,1表示最低优先级,10表示最高优先级。然而,线程优先级的具体实现依赖于操作系统,不能保证高优先级的线程一定会先于低优先级的线程执行。
- 每个线程都有一个优先级,优先级高的线程在运行时通常会具有更高的优先权。线程优先级是一个
-
什么是线程调度器(Thread Scheduler)和时间分片(Time Slicing)?
- 线程调度器:操作系统服务,负责为
Runnable
状态的线程分配CPU时间。 - 时间分片:将可用的CPU时间分配给可用的
Runnable
线程的过程。分配CPU时间可以基于线程优先级或线程等待的时间。线程调度不受Java虚拟机控制,因此应用程序应尽量避免依赖线程优先级。
- 线程调度器:操作系统服务,负责为
-
在多线程中,什么是上下文切换(context-switching)?
- 上下文切换是存储和恢复CPU状态的过程,使得线程执行能够从中断点恢复执行。上下文切换是多任务操作系统和多线程环境的基本特征。
-
你如何确保main()方法所在的线程是Java程序最后结束的线程?
- 使用
Thread.join()
方法可以确保所有程序创建的线程在main()
方法退出前结束。join()
方法会使当前线程等待指定的线程执行完毕。
- 使用
-
线程之间是如何通信的?
- 当线程间可以共享资源时,线程间通信是协调它们的重要手段。
Object
类中的wait()
,notify()
,notifyAll()
方法可以用于线程间通信,管理资源的锁状态。
- 当线程间可以共享资源时,线程间通信是协调它们的重要手段。
-
为什么线程通信的方法wait(), notify()和notifyAll()被定义在Object类里?
- Java的每个对象都有一个锁(monitor),
wait()
,notify()
等方法用于等待对象的锁或通知其他线程对象的监视器可用。这些方法是Object
类的一部分,确保每个类都有用于线程间通信的基本方法。
- Java的每个对象都有一个锁(monitor),
-
为什么wait(), notify()和notifyAll()必须在同步方法或者同步块中被调用?
- 调用
wait()
方法时,线程必须拥有该对象的锁,然后释放锁并进入等待状态。调用notify()
方法时,线程也会释放锁,以便其他等待的线程可以获取锁。因此,这些方法必须在同步方法或同步块中调用,以确保线程持有对象的锁。
- 调用
-
为什么Thread类的sleep()和yield()方法是静态的?
Thread.sleep()
和yield()
方法在当前正在执行的线程上运行。这些方法是静态的,因为它们在其他处于等待状态的线程上调用没有意义。静态方法可以在当前正在执行的线程中工作,并避免程序员错误地认为可以在其他非运行线程上调用这些方法。
-
如何确保线程安全?
- 同步:使用
synchronized
关键字或ReentrantLock
等锁机制。 - 原子类:使用
java.util.concurrent.atomic
包中的原子类。 - 并发锁:使用
java.util.concurrent.locks
包中的锁。 - volatile关键字:确保变量的可见性和禁止指令重排序。
- 不变类:使用不可变类,如
String
。 - 线程安全类:使用线程安全的集合类,如
ConcurrentHashMap
。
- 同步:使用
-
volatile关键字在Java中有什么作用?
volatile
关键字确保变量的可见性,即一个线程对变量的修改会立即反映到主内存中,其他线程可以立即看到最新的值。volatile
还禁止指令重排序,确保程序的正确性。
-
同步方法和同步块,哪个是更好的选择?
- 同步块是更好的选择,因为它不会锁住整个对象。同步方法会锁住整个对象,即使类中有多个不相关联的同步块,也会导致它们停止执行并等待获取对象锁。同步块可以更细粒度地控制锁的范围。
-
如何创建守护线程?
- 使用
Thread.setDaemon(true)
方法可以将线程设置为守护线程。需要注意的是,必须在调用start()
方法前调用setDaemon(true)
,否则会抛出IllegalThreadStateException
异常。
- 使用
-
什么是ThreadLocal?
ThreadLocal
用于创建线程的本地变量。每个线程都会拥有它们自己的ThreadLocal
变量,可以使用get()
和set()
方法获取和设置值。ThreadLocal
实例通常是private static
属性,用于与线程状态关联。
-
什么是Thread Group?为什么建议使用它?
- ThreadGroup是一个类,提供关于线程组的信息。
ThreadGroup
的主要功能包括获取线程组中处于活跃状态的线程列表和设置未捕获异常处理器。然而,ThreadGroup
API较为薄弱,不建议继续使用。Java 1.5中Thread
类也添加了setUncaughtExceptionHandler
方法,提供了类似的功能。
- ThreadGroup是一个类,提供关于线程组的信息。
-
什么是Java线程转储(Thread Dump),如何得到它?
- 线程转储是一个JVM活动线程的列表,用于分析系统瓶颈和死锁。可以通过多种方式获取线程转储,包括使用Profiler、
kill -3
命令、jstack
工具等。jstack
工具是JDK自带的,使用方便,可以编写脚本定时生成线程转储以待分析。
- 线程转储是一个JVM活动线程的列表,用于分析系统瓶颈和死锁。可以通过多种方式获取线程转储,包括使用Profiler、
-
什么是死锁(Deadlock)?如何分析和避免死锁?
- 死锁是指两个或多个线程永远阻塞的情况,通常涉及多个资源。分析死锁需要查看Java应用程序的线程转储,找出状态为
BLOCKED
的线程及其等待的资源。避免死锁的常见方法包括避免嵌套锁、只在必要时使用锁、避免无限期等待等。
- 死锁是指两个或多个线程永远阻塞的情况,通常涉及多个资源。分析死锁需要查看Java应用程序的线程转储,找出状态为
-
什么是Java Timer类?如何创建一个有特定时间间隔的任务?
- Timer类是一个工具类,用于安排一个线程在未来某个特定时间执行。
Timer
类可以安排一次性任务或周期任务。TimerTask
是一个实现了Runnable
接口的抽象类,需要继承它来创建自己的定时任务,并使用Timer
安排执行。
- Timer类是一个工具类,用于安排一个线程在未来某个特定时间执行。
-
什么是线程池?如何创建一个Java线程池?
- 线程池管理一组工作线程,并维护一个用于放置等待执行的任务的队列。
java.util.concurrent.Executors
提供了一个java.util.concurrent.Executor
接口的实现,用于创建线程池。常见的线程池类型包括固定大小线程池、缓存线程池、单线程执行器等。
- 线程池管理一组工作线程,并维护一个用于放置等待执行的任务的队列。
Java并发问题
-
什么是原子操作?在Java Concurrency API中有哪些原子类(atomic classes)?
- 原子操作是指一个不受其他操作影响的操作任务单元。
int++
不是原子操作,可能会导致数据不一致。Java Concurrency API中的java.util.concurrent.atomic
包提供了AtomicInteger
和AtomicLong
等原子类,确保操作的原子性。
- 原子操作是指一个不受其他操作影响的操作任务单元。
-
Java Concurrency API中的Lock接口(Lock interface)是什么?对比同步它有什么优势?
- Lock接口提供了比同步方法和同步块更灵活的锁操作。优势包括:
- 更公平的锁机制。
- 支持中断等待锁的线程。
- 尝试获取锁时可以选择立即返回或等待一段时间。
- 支持在不同范围以不同顺序获取和释放锁。
- Lock接口提供了比同步方法和同步块更灵活的锁操作。优势包括:
-
什么是Executors框架?
- Executors框架是Java 5引入的一个框架,用于调用、调度、执行和控制异步任务。创建线程池可以限制线程数量并回收再利用线程,避免无限制创建线程导致内存溢出。
Executors
类提供了多种创建线程池的方法。
- Executors框架是Java 5引入的一个框架,用于调用、调度、执行和控制异步任务。创建线程池可以限制线程数量并回收再利用线程,避免无限制创建线程导致内存溢出。
-
什么是阻塞队列?如何使用阻塞队列来实现生产者-消费者模型?
- 阻塞队列是当队列为空时,从队列中获取或删除元素的操作会被阻塞,当队列满时,往队列中添加元素的操作会被阻塞。
java.util.concurrent.BlockingQueue
接口是Java collections框架的一部分,适用于实现生产者-消费者模型。
- 阻塞队列是当队列为空时,从队列中获取或删除元素的操作会被阻塞,当队列满时,往队列中添加元素的操作会被阻塞。
-
什么是Callable和Future?
- Callable定义了需要异步执行的任务,返回一个结果并可能抛出异常。
- Future提供了异步执行结果的获取和管理方式,可以检查任务是否完成、取消任务、获取任务结果等。
-
什么是FutureTask?
- FutureTask是
Future
的一个基础实现,可以将Callable
任务包装成FutureTask
,并提交给Executor
执行。FutureTask
可以用于实现异步任务的管理和结果获取。
- FutureTask是
-
什么是并发容器的实现?
- 并发容器支持并发的遍历和更新,避免了
ConcurrentModificationException
异常。主要的并发容器包括ConcurrentHashMap
,CopyOnWriteArrayList
和CopyOnWriteArraySet
。
- 并发容器支持并发的遍历和更新,避免了
-
Executors类是什么?
- Executors类为
Executor
,ExecutorService
,ScheduledExecutorService
,ThreadFactory
和Callable
类提供了一些工具方法,方便创建线程池和其他并发工具。
- Executors类为