
基本概念程序program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码静态对象。进程(process)是程序的一次执行过程或是正在运行的一个程序。进程作为资源分配的单位系统在运行时会为每个进程分配不同的内存区域。是一个动态的过程有它自身的产生、存在和消亡的过程称为生命周期。线程(thread进程可进一步细化为线程是一个程序内部的一条执行路径。若一个进程同一时间并行执行多个线程就是支持多线程的。线程作为调度和执行的单位每个线程拥有独立的运行栈和程序计数器pc线程切换的开销小。一个进程中的多个线程共享相同的内存单元/内存地址空间→它们从同一堆中分配对象可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患。并行多个任务在同一时刻由多个处理器或核心同时执行强调的是物理上的同时性。例如在多核处理器上两个不同的程序或线程可以真正地同时运行各自占用一个核心。并发多个任务在逻辑上交错执行可能在同一个时间段内交替进行但不一定同时发生。它更关注的是任务的调度和管理使得系统能够高效处理多个任务即使在单核处理器上也能实现“看似同时”运行的效果。实现方式Java 中创建线程主要有以下方式继承Thread类重写run()方法然后调用start()启动线程。实现Runnable接口将任务与线程分离更灵活推荐使用。实现Callable接口配合FutureTask使用可以获取线程执行结果或抛出异常。线程池通过ExecutorService管理线程避免频繁创建和销毁线程。继承Thread类执行步骤创建一个继承Thread类的子类重写Thread类的run方法——此线程执行的操作执行在run方法中创建一个Thread类子类的对象通过此对象调用start方法public class StartThread extends Thread { // 重写 run() 方法定义线程执行的具体任务 Override public void run() { // 输出当前线程的运行状态信息 System.out.println(线程1运行中); } // 程序入口点主方法 public static void main(String[] args) { // 创建 StartThread 类的实例对象 StartThread st new StartThread(); // 调用 start() 方法启动新线程系统会自动调用 run() 方法 st.start(); // 主线程继续执行输出另一条信息 System.out.println(线程2运行中); } }执行结果说明st.start()启动一个独立的线程线程1调用当前线程的run()方法。主线程线程2在start()调用后立即继续执行后续代码输出“线程2运行中”。由于线程调度的不确定性输出顺序可能为线程1运行中线程2运行中或线程2运行中线程1运行中注意事项不应直接调用run()方法否则不会创建新线程而是在主线程中同步执行。正确方式是使用 start由 JVM 调度并创建新的执行路径。每个线程对象只能启动一次重复调用start()会抛出IllegalThreadStateException。可以重新创建一个线程对象再调用。线程的调度调度策略时间片抢占式高优先级的线程抢占CPU调度方法同优先级线程组成先进先出队列使用时间片策略对高优先级使用优先调度的抢占式策略线程的优先级等级MAX_PRIORITY10MIN_PRIORITY1NORM_PRIORITY5——默认优先级涉及的方法getPriority返回线程优先值setPriorityint newPriority改变线程的优先级public class TestThread { public static void main(String[] args) { MyThread1 t1 new MyThread1(); MyThread2 t2 new MyThread2(); t1.setPriority(Thread.MAX_PRIORITY); t2.setPriority(Thread.MIN_PRIORITY); t1.setName(线程一); t2.setName(线程二); t1.start(); t2.start(); } static class MyThread1 extends Thread { Override public void run() { for (int i 0; i 100; i) { if (i % 2 0) { System.out.println(Thread.currentThread().getName() : Thread.currentThread().getPriority() : i); } } } } static class MyThread2 extends Thread { Override public void run() { for (int i 0; i 100; i) { if (i % 2 ! 0) { System.out.println(Thread.currentThread().getName() : Thread.currentThread().getPriority() : i); } } } } }高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲高优先级的线程高概率的情况下被执行。并不意味着只有当高优先级的线程执行完以后低优先级的线程才执行。实现Runnable接口执行步骤创建一个实现Runnable接口的类在该类中实现Runnable接口中定义的抽象方法run()创建该实现类的实例对象将该对象作为参数传入Thread类的构造器创建一个Thread对象通过Thread对象调用start()方法// 1. 创建一个实现 Runnable 接口的类 class MyThread implements Runnable { // 2. 实现 Runnable 接口中的抽象方法 run() Override public void run() { for (int i 0; i 100; i) { if (i % 2 0) { // 因未继承 Thread 类无法直接调用 getName()需通过 Thread.currentThread() 获取当前线程名称 System.out.println(Thread.currentThread().getName() : i); } } } } public class ThreadTest1 { public static void main(String[] args) { // 3. 创建实现类的对象 MyThread mt new MyThread(); // 4. 将该对象作为参数传递给 Thread 构造器创建 Thread 对象 Thread t1 new Thread(mt); t1.setName(线程一); // 5. 调用 start() 方法① 启动线程② 执行当前线程的 run() —— 实际调用的是 Runnable 目标对象的 run() t1.start(); // 启动第二个线程同样遍历 100 以内的偶数 Thread t2 new Thread(mt); t2.setName(线程二); t2.start(); } }比较创建线程的两种方式方式类的身份是否有 start ()使用方式extends Thread既是线程对象又是任务✅ 继承得到 start ()对象.start ()implements Runnable仅仅是任务不是线程❌ 没有 start ()new Thread (任务).start ()Thread本身就是线程对象它里面包含start()方法用来创建操作系统线程。Runnable只是任务只包含要执行的run()本身不是线程必须交给Thread对象来启动。优先选择实现Runnable接口的方式原因如下实现的方式没有类的单继承性的局限性实现的方式更适合来处理多个线程有共享数据的情况。线程的生命周期新建New线程对象被创建但尚未启动。此时线程仅存在于内存中未与操作系统线程关联也未开始执行任何任务。例如Thread thread new Thread();。可运行Runnable线程已调用start()方法进入可运行状态。此时线程由操作系统调度具备运行的资格但是否正在运行取决于系统资源分配。该状态包括就绪和运行两个子状态。运行中Running线程已被操作系统选中并正在执行其run()方法中的代码。这是线程实际执行任务的阶段属于可运行状态的一部分。阻塞Blocked线程因等待某个条件而暂停执行无法继续运行。常见原因包括等待获取锁如进入synchronized代码块或方法调用wait()、sleep()、join()等方法等待 I/O 操作完成。在此状态下线程不占用处理器时间但保持在内存中等待外部事件触发。终止Terminated线程执行完毕或因异常退出或被强制终止如调用stop()虽不推荐。线程不再参与调度其资源被系统回收生命周期结束。此时线程对象仍存在但不可再次启动。线程安全线程安全性是指在多线程环境下程序的行为符合预期不会因为多个线程同时访问共享资源而产生错误结果。一个线程安全的类或方法能够在并发访问时保持数据的一致性和正确性不出现竞态条件race condition、脏读、死锁等异常行为。举例创建三个窗口卖票总票数为100张使用实现Runnable接口的方式。问题卖票过程中出现了重票、错票即出现了线程的安全问题。问题出现的原因当某个线程操作车票的过程中尚未操作完成时其他线程参与进来也操作车票。如何解决当一个线程a在操作ticket的时候其他线程不能所以你参与进来。直到线程a操作完ticket时线程才可以开始操作ticket。这种情况即使线程a出现了阻塞也不能被改变。class Window implements Runnable { private int ticket 100; Override public void run() { while (ticket 0) { try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } System.out.println(Thread.currentThread().getName() 卖票票号为 ticket); ticket--; } } } public class WindowTest1 { public static void main(String[] args) { Window window new Window(); Thread t1 new Thread(window, 窗口一); Thread t2 new Thread(window, 窗口二); Thread t3 new Thread(window, 窗口三); t1.start(); t2.start(); t3.start(); } }执行结果线程二:卖票票号为100 窗口一:卖票票号为100 线程三:卖票票号为100 窗口一:卖票票号为97 线程三:卖票票号为97 线程二:卖票票号为97 窗口一:卖票票号为94同步机制方法一同步代码块synchronized(同步监视器){ //需要被同步的代码 }操作共享数据的代码即为需要被同步的代码。共享数据多个线程共同操作的变量。比如ticket就是共享数据。同步监视器俗称锁。任何一个类的对象都可以充当锁。多个线程必须要共用同一把锁。在实现Runnable接口创建多线程的方式中我们可以考虑使用this充当同步监视器。在继承Thread类创建多线程的方式中慎用this充当同步监视器考虑使用当前类充当监视器。同步的方式解决了线程的安全问题。但操作同步代码时只能有一个线程参与其他线程等待。相当于是一个单线程的过程效率低。使用 synchronized 关键字实现线程安全实现Runnable接口class Window implements Runnable { private int ticket 100; private final Object lock new Object(); Override public void run() { while (true) { synchronized (lock) { if (ticket 0) { try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } System.out.println(Thread.currentThread().getName() : 售出票号为 ticket); ticket--; } else { break; } } } } }继承Thread类class Window1 extends Thread { private static int ticket 100; private static final Object obj new Object(); Override public void run() { while (true) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (obj) { if (ticket 0) { System.out.println(Thread.currentThread().getName() 卖票票号为 ticket); ticket--; } else { break; } } } } } public class TestThread02 { public static void main(String[] args) { Window1 w1 new Window1(); Window1 w2 new Window1(); Window1 w3 new Window1(); w1.setName(窗口一); w2.setName(窗口二); w3.setName(窗口三); w1.start(); w2.start(); w3.start(); } }代码特征是否超卖运行现象sleep 在锁内否单窗口连续卖票sleep 在锁外 锁内 if 判断否窗口交替卖票标准 demo只有锁无 sleep否大概率单窗口一次性卖完while 外层判断 ticket0锁内没有 if✅可能超卖最后卖出负数票sleep(long ms)让当前线程休眠指定毫秒休眠期间线程不会释放已经持有的锁最重要sleep 是静态方法Thread.sleep()作用的是当前正在执行这行代码的线程不是别的线程。sleep 会抛出受检异常InterruptedException必须 try-catch 或者 throws。休眠时间只是最小等待时间时间到了线程进入就绪状态不一定立刻拿到 CPU 执行。sleep 只管睡觉不放手锁锁里面尽量不要写 sleep。方法二同步方法实现Runnable接口class Window3 implements Runnable { private int ticket 100; Override public void run() { while (true) { try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); // 恢复中断状态 break; } if (!show()) { break; } } } private synchronized boolean show() { if (ticket 0) { System.out.println(Thread.currentThread().getName() 卖票票号为 ticket); ticket--; return true; } else { return false; } } } public class WindowTest3 { public static void main(String[] args) { Window3 window new Window3(); Thread t1 new Thread(window, 窗口一); Thread t2 new Thread(window, 窗口二); Thread t3 new Thread(window, 窗口三); t1.start(); t2.start(); t3.start(); } }继承Thread类class Window4 extends Thread { private static int ticket 100; Override public void run() { while (true) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } if (!show()) { break; } } } public static synchronized boolean show() { if (ticket 0) { System.out.println(Thread.currentThread().getName() 卖票票号为 ticket); ticket--; return true; } else { return false; } } } public class WindowTest4 { public static void main(String[] args) { Window4 w1 new Window4(); Window4 w2 new Window4(); Window4 w3 new Window4(); w1.setName(窗口一); w2.setName(窗口二); w3.setName(窗口三); w1.start(); w2.start(); w3.start(); } }同步方法仍涉及到同步监视器只是不需要显式声明非静态的同步监视器this静态同步监视器当前类本身Window4.class死锁不同的线程分别占用对方需要的同步资源不放弃都在等待对方放弃自己需要的同步资源就形成了线程的死锁。出现死锁后不会出现异常不会出现提示只是所有的线程都处于阻塞状态无法继续。// 线程A lock(A); lock(B); // 可能阻塞因为线程B已持有B // 执行临界区代码 // 线程B lock(B); lock(A); // 可能阻塞因为线程A已持有A // 执行临界区代码如何避免编程中的死锁统一加锁顺序所有线程按照相同的顺序获取锁。使用超时机制尝试获取锁时设置超时时间避免无限等待。避免嵌套锁尽量减少多锁嵌套操作。使用高级同步工具如Java中的ReentrantLock配合tryLock()方法可实现非阻塞式锁获取。Lock锁解决线程安全import java.util.concurrent.locks.ReentrantLock; class Window01 implements Runnable { private int ticket 100; private final ReentrantLock lock new ReentrantLock(); Override public void run() { while (true) { lock.lock(); try { if (ticket 0) { System.out.println(Thread.currentThread().getName() 卖票票号 ticket); ticket--; Thread.sleep(100); // 模拟卖票耗时 } else { break; } } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } finally { lock.unlock(); } } } public static void main(String[] args) { Window01 window new Window01(); Thread t1 new Thread(window, 窗口1); Thread t2 new Thread(window, 窗口2); Thread t3 new Thread(window, 窗口3); t1.start(); t2.start(); t3.start(); } }synchronized与Lock的异同相同二者都可以解决线程安全问题不同synchronized机制在执行完相应的同步代码以后自动的释放同步监视器Lock需要手动的启动同步lock同时结束同步也需要手动的实现unlock线程通信/** * Number 类实现 Runnable 接口用于多线程环境下按顺序打印 1 到 100 的数字。 * 通过 synchronized 同步块和 wait/notify 机制实现线程间的协调。 */ class Number implements Runnable { private int number 1; // 当前待打印的数字初始为 1 Override public void run() { while (true) { try { Thread.sleep(100); // 模拟处理延迟避免过快输出 } catch (InterruptedException e) { e.printStackTrace(); // 处理中断异常 } synchronized (this) { // 唤醒其他等待线程若存在确保下一个线程可以继续执行 notify(); // 判断是否已打印完所有数字 if (number 100) { // 输出当前线程名称及当前数字 System.out.println(Thread.currentThread().getName() : number); number; // 数字递增 try { // 当前线程调用 wait() 进入阻塞状态释放锁等待被唤醒 wait(100); // 等待最多 100 毫秒或被 notify 唤醒 } catch (InterruptedException e) { e.printStackTrace(); // 处理中断异常 } } else { // 打印完毕退出循环 break; } } } } }wait()执行该方法后当前线程进入阻塞状态并释放所持有的同步监视器。notify()执行该方法后唤醒一个正在等待的线程。若有多个线程处于等待状态则唤醒优先级较高的那个。notifyAll()执行该方法后唤醒所有正在等待的线程。wait()、notify()和notifyAll()三个方法必须在同步代码块或同步方法中调用。它们的调用者必须是同步代码块或同步方法中的同步监视器即锁对象否则会抛出IllegalMonitorStateException异常。这三个方法定义在java.lang.Object类中。sleep() 和 wait()的区别特性sleep()wait()所属类ThreadObject是否释放锁否是唤醒方式时间到期notify()/notifyAll() / 中断调用环境要求无限制必须在同步块/方法中适用场景延迟执行、定时任务线程协作、条件等待实现Callable接口与使用Runnable相比Callable功能更强大相比run方法可以有返回值方法可以抛出异常支持泛型的返回值需要借助FutureTask类比如获取返回结果import java.util.concurrent.Callable; public class MyCallable implements CallableString { Override public String call() throws Exception { // 任务逻辑 return 任务执行完成返回结果; } }示例import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class TestCallable { public static void main(String[] args) { // 3. 创建 Callable 接口实现类的对象 Numbertest task new Numbertest(); // 4. 将 Callable 实现类对象传入 FutureTask 构造器创建 FutureTask 对象 FutureTaskInteger futureTask new FutureTask(task); // 5. 将 FutureTask 对象作为参数创建 Thread 并启动 new Thread(futureTask).start(); try { // 6. 获取 call() 方法的返回值 // get() 返回值即为 Callable 中重写的 call() 方法的返回结果 Integer sum futureTask.get(); System.out.println(总和为 sum); } catch (InterruptedException | ExecutionException e) { throw new RuntimeException(e); } } } // 1. 创建实现 Callable 接口的类 class Numbertest implements CallableInteger { // 2. 重写 call() 方法定义线程执行的任务逻辑 Override public Integer call() throws Exception { int sum 0; for (int i 0; i 100; i) { if (i % 2 0) { System.out.println(i); sum i; } } return sum; } }线程池背景经常创建和销毁、使用量特别大的资源比如并发情况下的线程对性能影响很大。思路提前创建好多个线程放入线程池中使用时直接获取使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。好处提高响应速度减少了创建新线程的时间降低资源消耗重复利用线程池中线程不需要每次都创建便于线程管理corePoolSize:核心池的大小maximumPoolSize最大线程数keepAliveTime线程没有任务时最多保持多长时间后会终止创建线程池的方法使用java.util.concurrent.Executors工具类可以快速创建不同类型的线程池import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; // 固定大小线程池5个线程 ExecutorService fixedThreadPool Executors.newFixedThreadPool(5); // 单线程执行器 ExecutorService singleThreadExecutor Executors.newSingleThreadExecutor(); // 可缓存线程池根据需要创建新线程空闲线程可复用 ExecutorService cachedThreadPool Executors.newCachedThreadPool(); // 定时任务线程池 ExecutorService scheduledThreadPool Executors.newScheduledThreadPool(3);提交任务到线程池可以通过execute(Runnable)或submit(CallableT)方法提交任务// 提交无返回值的任务 fixedThreadPool.execute(() - { System.out.println(任务正在运行线程 Thread.currentThread().getName()); }); // 提交有返回值的任务 import java.util.concurrent.Callable; CallableString task () - { Thread.sleep(1000); return 任务完成结果为Hello World; }; try { String result fixedThreadPool.submit(task).get(); System.out.println(result); } catch (Exception e) { e.printStackTrace(); }关闭线程池必须在不再提交新任务后关闭线程池防止内存泄漏或程序无法退出// 平滑关闭不再接受新任务等待已有任务完成 fixedThreadPool.shutdown(); // 强制关闭立即停止所有任务不等待 // fixedThreadPool.shutdownNow();检查是否已完全关闭if (fixedThreadPool.isShutdown()) { System.out.println(线程池已关闭); } if (fixedThreadPool.isTerminated()) { System.out.println(所有任务已结束); }自定义线程池推荐方式使用ThreadPoolExecutor构造函数实现更精细的控制import java.util.concurrent.*; ThreadPoolExecutor customPool new ThreadPoolExecutor( 2, // 核心线程数 4, // 最大线程数 60L, // 空闲线程存活时间秒 TimeUnit.SECONDS, new LinkedBlockingQueue(10), // 任务队列 new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略 );核心线程数始终存在的线程数量。最大线程数允许创建的最大线程数。空闲超时时间超过核心线程数的线程在空闲时等待的时间。任务队列存放待执行任务的缓冲区。拒绝策略当任务队列满且线程数已达上限时的处理方式。常见拒绝策略AbortPolicy抛出异常。DiscardPolicy直接丢弃任务。DiscardOldestPolicy丢弃最老的任务尝试重新插入。CallerRunsPolicy由调用线程直接执行任务。使用建议与最佳实践避免使用Executors.newCachedThreadPool()因其可能导致无限创建线程引发资源耗尽。尽量使用ThreadPoolExecutor自定义线程池以便精确控制资源使用。合理设置线程池大小通常参考 CPU 核心数和任务类型计算密集型或 I/O 密集型。对于长时间运行的任务应考虑设置合理的超时机制。在高并发场景下监控线程池状态如活跃线程数、队列长度有助于排查性能瓶颈。示例完整使用流程public class ThreadPoolExample { public static void main(String[] args) { ExecutorService executor new ThreadPoolExecutor( 2, 4, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue(5), new ThreadPoolExecutor.CallerRunsPolicy() ); for (int i 0; i 10; i) { final int taskId i; executor.submit(() - { System.out.println(任务 taskId 正在执行线程 Thread.currentThread().getName()); try { Thread.sleep(2000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } }); } executor.shutdown(); try { if (!executor.awaitTermination(30, TimeUnit.SECONDS)) { executor.shutdownNow(); } } catch (InterruptedException e) { executor.shutdownNow(); Thread.currentThread().interrupt(); } } }