03 - Java 多线程

本章节聚焦于 Java 多线程编程(Concurrency),这是高并发系统开发的核心。内容涵盖了线程与进程的区别、线程创建方式、生命周期、锁机制(Synchronized, Lock, ReentrantLock)、线程池原理、死锁问题以及 CAS 和 Atomic 原子类等高级主题
<h3>1. 并行和并发有什么区别?</h3>
<ul>
<li><strong>并发 (Concurrency)</strong>:指多个任务在<strong>同一个 CPU 核</strong>上,按时间片轮转交替执行,宏观上看起来是同时执行,微观上是串行。</li>
<li><strong>并行 (Parallelism)</strong>:指多个任务在<strong>不同的 CPU 核</strong>上同时执行,是真正的物理上的同时。</li>
</ul>
<h3>2. 线程和进程的区别?</h3>
<ul>
<li><strong>进程</strong>:是操作系统资源分配的基本单位。每个进程拥有独立的内存空间。</li>
<li><strong>线程</strong>:是 CPU 调度的基本单位。线程依附于进程,共享进程的内存(堆、方法区),但拥有独立的栈和程序计数器。</li>
<li><strong>开销</strong>:进程切换开销大,线程切换开销小。</li>
</ul>
<h3>3. 守护线程是什么?</h3>
<p>守护线程(Daemon Thread)是为其他线程服务的线程,比如垃圾回收线程(GC)。</p>
<ul>
<li>当 JVM 中所有的非守护线程(用户线程)结束时,JVM 会退出,不管守护线程是否还在运行。</li>
<li>可以通过 <code>thread.setDaemon(true)</code> 设置。</li>
</ul>
<h3>4. 创建线程有哪几种方式?</h3>
<ol>
<li>继承 <strong>Thread</strong> 类,重写 <code>run()</code> 方法。</li>
<li>实现 <strong>Runnable</strong> 接口,重写 <code>run()</code> 方法。</li>
<li>实现 <strong>Callable</strong> 接口,重写 <code>call()</code> 方法(有返回值,可抛异常)。</li>
<li>使用 <strong>线程池</strong>(ExecutorService)。</li>
</ol>
<h3>5. 说一下 runnable 和 callable 有什么区别?</h3>
<ul>
<li><strong>返回值</strong>:Runnable 的 <code>run()</code> 没有返回值;Callable 的 <code>call()</code> 有返回值(Future)。</li>
<li><strong>异常</strong>:Runnable 不能抛出 Checked Exception;Callable 可以抛出异常。</li>
<li><strong>使用</strong>:Callable 需要配合 FutureTask 或线程池使用。</li>
</ul>
<h3>6. 线程有哪些状态?</h3>
<p>Java 线程有 6 种状态(Thread.State):</p>
<ol>
<li><strong>NEW</strong>:新建,未启动。</li>
<li><strong>RUNNABLE</strong>:运行中(包括 Ready 和 Running)。</li>
<li><strong>BLOCKED</strong>:阻塞,等待锁。</li>
<li><strong>WAITING</strong>:无限期等待(wait, join, park)。</li>
<li><strong>TIMED_WAITING</strong>:限时等待(sleep, wait(time))。</li>
<li><strong>TERMINATED</strong>:终止,执行结束。</li>
</ol>
<h3>7. sleep() 和 wait() 有什么区别?</h3>
<ul>
<li><strong>类</strong>:sleep 是 Thread 类的静态方法;wait 是 Object 类的方法。</li>
<li><strong>锁</strong>:sleep <strong>不会释放锁</strong>;wait <strong>会释放锁</strong>。</li>
<li><strong>唤醒</strong>:sleep 时间到自动唤醒;wait 需要 notify/notifyAll 唤醒(如果不设置超时)。</li>
<li><strong>使用</strong>:sleep 可以在任何地方使用;wait 必须在 synchronized 块中使用。</li>
</ul>
<h3>8. notify()和 notifyAll()有什么区别?</h3>
<ul>
<li><strong>notify()</strong>:随机唤醒一个等待该对象锁的线程。</li>
<li><strong>notifyAll()</strong>:唤醒所有等待该对象锁的线程,让它们去竞争锁。</li>
</ul>
<h3>9. 线程的 run()和 start()有什么区别?</h3>
<ul>
<li><strong>start()</strong>:启动线程,JVM 会调用该线程的 run() 方法。是真正的多线程。</li>
<li><strong>run()</strong>:只是一个普通的方法调用,如果在主线程调用 run(),依然是在主线程执行,没有开启新线程。</li>
</ul>
<h3>10. 创建线程池有哪几种方式?</h3>
<p>通过 <code>Executors</code> 工具类创建:</p>
<ol>
<li><code>newFixedThreadPool</code>:固定大小线程池。</li>
<li><code>newCachedThreadPool</code>:可缓存线程池(无限大)。</li>
<li><code>newSingleThreadExecutor</code>:单线程池。</li>
<li><code>newScheduledThreadPool</code>:定时任务线程池。</li>
</ol>
<p><em>推荐使用 <code>ThreadPoolExecutor</code> 构造函数手动创建,以明确参数避免 OOM。</em></p>
<h3>11. 线程池都有哪些状态?</h3>
<ul>
<li><strong>RUNNING</strong>:接收新任务,处理队列任务。</li>
<li><strong>SHUTDOWN</strong>:不接收新任务,但处理队列任务。</li>
<li><strong>STOP</strong>:不接收新任务,不处理队列任务,中断正在执行的任务。</li>
<li><strong>TIDYING</strong>:所有任务终止,workerCount 为 0。</li>
<li><strong>TERMINATED</strong>:terminated() 方法执行完。</li>
</ul>
<h3>12. 线程池中 submit()和 execute()方法有什么区别?</h3>
<ul>
<li><strong>execute()</strong>:提交 Runnable 任务,<strong>没有返回值</strong>。</li>
<li><strong>submit()</strong>:提交 Runnable 或 Callable 任务,<strong>返回 Future 对象</strong>,可以获取结果或捕获异常。</li>
</ul>
<h3>13. 在 java 程序中怎么保证多线程的运行安全?</h3>
<ol>
<li><strong>原子性</strong>:Atomic 包、synchronized、Lock。</li>
<li><strong>可见性</strong>:volatile、synchronized、Lock。</li>
<li><strong>有序性</strong>:volatile、synchronized、Lock。</li>
<li>使用线程安全的集合(ConcurrentHashMap 等)。</li>
<li>使用 ThreadLocal 避免共享。</li>
</ol>
<h3>14. 多线程锁的升级原理是什么?</h3>
<p>synchronized 锁升级过程:</p>
<p><strong>无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁</strong></p>
<ul>
<li><strong>偏向锁</strong>:只有一个线程访问锁,不需要同步。</li>
<li><strong>轻量级锁</strong>:当有第二个线程竞争时,升级为轻量级锁(自旋锁),通过 CAS 尝试获取锁。</li>
<li><strong>重量级锁</strong>:当自旋失败或竞争激烈时,升级为重量级锁,阻塞线程,进入内核态。</li>
</ul>
<h3>15. 什么是死锁?</h3>
<p>两个或多个线程互相持有对方需要的资源,且都不释放,导致永久等待。</p>
<p>条件:互斥、请求与保持、不剥夺、循环等待。</p>
<h3>16. 怎么防止死锁?</h3>
<ul>
<li>破坏循环等待条件:按顺序获取锁。</li>
<li>设置超时时间:Lock.tryLock(time)。</li>
<li>避免嵌套锁。</li>
</ul>
<h3>17. ThreadLocal 是什么?有哪些使用场景?</h3>
<p>ThreadLocal 提供<strong>线程局部变量</strong>。每个线程都有自己独立的变量副本,互不干扰。</p>
<ul>
<li><strong>原理</strong>:每个 Thread 内部维护一个 ThreadLocalMap,Key 是 ThreadLocal 对象,Value 是变量值。</li>
<li><strong>场景</strong>:数据库连接管理、Session 管理、SimpleDateFormat(非线程安全)的独享。</li>
<li><strong>注意</strong>:使用完需调用 <code>remove()</code> 防止内存泄漏(Key 是弱引用,Value 是强引用)。</li>
</ul>
<h3>18. 说一下 synchronized 底层实现原理?</h3>
<ul>
<li><strong>代码块</strong>:使用 <code>monitorenter</code> 和 <code>monitorexit</code> 指令。</li>
<li><strong>方法</strong>:使用 <code>ACC_SYNCHRONIZED</code> 标志。</li>
<li>本质是基于对象头中的 Monitor 对象(监视器锁),依赖操作系统的 Mutex Lock。</li>
</ul>
<h3>19. synchronized 和 volatile 的区别是什么?</h3>
<ul>
<li><strong>volatile</strong>:保证可见性、有序性,<strong>不保证原子性</strong>。轻量级。</li>
<li><strong>synchronized</strong>:保证可见性、有序性、<strong>原子性</strong>。重量级(但在 JDK 1.6 后优化)。</li>
</ul>
<h3>20. synchronized 和 Lock 有什么区别?</h3>
<ul>
<li><strong>层面</strong>:synchronized 是 JVM 层面(关键字);Lock 是 API 层面(接口)。</li>
<li><strong>释放</strong>:synchronized 自动释放锁;Lock 必须手动释放(finally 中 unlock)。</li>
<li><strong>灵活性</strong>:Lock 支持尝试获取锁(tryLock)、中断等待、公平锁等;synchronized 不支持。</li>
</ul>
<h3>21. synchronized 和 ReentrantLock 区别是什么?</h3>
<ul>
<li>ReentrantLock 是 Lock 的实现类。</li>
<li>ReentrantLock 增加了:</li>
</ul>
<ol>
<li><strong>等待可中断</strong>。</li>
<li><strong>可实现公平锁</strong>。</li>
<li><strong>可绑定多个 Condition</strong>(实现分组唤醒)。</li>
</ol>
<h3>22. 说一下 atomic 的原理?</h3>
<p>Atomic 包(如 AtomicInteger)利用 <strong>CAS (Compare And Swap)</strong> 乐观锁机制。</p>
<ul>
<li>CAS 包含三个操作数:内存值 V、预期原值 A、新值 B。</li>
<li>只有当 V == A 时,才将 V 更新为 B,否则重试。</li>
<li>底层依赖 Unsafe 类的 native 方法(CPU 指令 cmpxchg)。</li>
</ul>
评论 (0)
加载评论中…