<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet href="/feeds/atom-style.xsl" type="text/xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://luhaodai.github.io/zh/</id>
    <title>online-blog-site</title>
    <updated>2026-07-31T09:09:05.167Z</updated>
    <generator>Astro-Theme-Retypeset with Feed for Node.js</generator>
    <author>
        <name>luhaodai</name>
        <uri>https://luhaodai.github.io/</uri>
    </author>
    <link rel="alternate" href="https://luhaodai.github.io/zh/"/>
    <link rel="self" href="https://luhaodai.github.io/zh/atom.xml"/>
    <subtitle>A personal blog site built with Astro and Retypeset theme, sharing thoughts on technology, life, and more.</subtitle>
    <rights>Copyright © 2026 luhaodai</rights>
    <entry>
        <title type="html"><![CDATA[多线程快速入门]]></title>
        <id>https://luhaodai.github.io/zh/posts/java-multithread-quickstart/</id>
        <link href="https://luhaodai.github.io/zh/posts/java-multithread-quickstart/"/>
        <updated>2026-07-12T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Java 多线程从基础到实战的核心知识点，涵盖进程线程概念、三种创建线程方式、线程安全与同步、死锁、以及多线程分批发送短信实战案例。]]></summary>
        <content type="html"><![CDATA[<p>本知识文档基于 <strong>【www.zxit8.com】 0001-多线程快速入门</strong> 课程整理，涵盖 Java 多线程从基础到实战的核心知识点。</p>
<h2>进程与线程基础概念</h2>
<h3>什么是进程？</h3>
<p><strong>进程（Process）</strong> 其实就是一个应用程序，进程是所有线程的集合。每个正在运行的程序就是一个进程，进程拥有独立的资源（内存空间、文件句柄等），进程之间相互隔离。</p>
<h3>什么是线程？</h3>
<p><strong>线程（Thread）</strong> 其实就是一条执行路径。主要包括：</p>
<ul>
<li><strong>main 主线程</strong>：程序入口，JVM 自动创建</li>
<li><strong>子线程</strong>：开发者自己创建的线程</li>
<li><strong>gc 线程</strong>：JVM 专门用于垃圾回收的线程</li>
</ul>
<h3>单任务 vs 多任务</h3>
<table>
<thead>
<tr>
<th>概念</th>
<th>说明</th>
<th>类比</th>
</tr>
</thead>
<tbody>
<tr>
<td>单任务</td>
<td>一次只做一件事，做完才能做下一件</td>
<td>排队买票，一个人买完才到下一个人</td>
</tr>
<tr>
<td>多任务</td>
<td>同一时间做多件事</td>
<td>边听音乐边写代码</td>
</tr>
</tbody>
</table>
<p>多线程的目的：提高 CPU 利用率，让程序"同时"执行多个任务。</p>
<h3>CPU 时间片与随机切换</h3>
<p>CPU 通过 <strong>时间片轮转</strong> 在各个线程之间快速切换，切换速度极快，给人"同时运行"的错觉。<strong>线程的随机性</strong> 是每次执行顺序可能不同，这是多线程编程的难点之一。</p>
<h2>三种创建线程的方式</h2>
<p>Java 中创建线程主要有三种方式，难度递进。</p>
<h3>继承 Thread 类</h3>
<p><strong>步骤：</strong> 定义一个类，继承 <code>Thread</code> 类，重写 <code>run()</code> 方法，调用 <code>start()</code> 方法启动线程。</p>
<pre><code>class CreateThread extends Thread {
    public void run() {
        for (int i = 0; i &lt; 10; i++) {
            System.out.println("i:" + i);
        }
    }
}

public class ThreadDemo {
    public static void main(String[] args) {
        CreateThread createThread = new CreateThread();
        createThread.start();  // 启动线程，不是调用 run()
    }
}
</code></pre>
<p>启动线程必须调用 <code>start()</code> 方法，而不是 <code>run()</code> 方法。调用 <code>run()</code> 相当于在主线程中执行普通方法。</p>
<h3>实现 Runnable 接口</h3>
<p><strong>步骤：</strong> 定义一个类，实现 <code>Runnable</code> 接口，重写 <code>run()</code> 方法，将该类的实例作为参数传入 <code>Thread</code> 的构造器，调用 <code>start()</code> 方法启动。</p>
<pre><code>class CreateRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i &lt; 10; i++) {
            System.out.println("i:" + i);
        }
    }
}

public class ThreadDemo2 {
    public static void main(String[] args) {
        CreateRunnable task = new CreateRunnable();
        Thread thread = new Thread(task);
        thread.start();
    }
}
</code></pre>
<h3>匿名内部类</h3>
<p>适用场景：线程任务简单，不需要复用的情况。</p>
<pre><code>public class ThreadDemo3 {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            public void run() {
                for (int i = 0; i &lt; 10; i++) {
                    System.out.println("i:" + i);
                }
            }
        });
        thread.start();
    }
}
</code></pre>
<h3>三种方式对比</h3>
<table>
<thead>
<tr>
<th>方式</th>
<th>优点</th>
<th>缺点</th>
<th>适用场景</th>
</tr>
</thead>
<tbody>
<tr>
<td>继承 Thread</td>
<td>简单直接</td>
<td>Java 单继承，无法继承其他类</td>
<td>简单的独立线程</td>
</tr>
<tr>
<td>实现 Runnable</td>
<td>灵活，可继承其他类</td>
<td>稍复杂</td>
<td>资源共享、推荐使用</td>
</tr>
<tr>
<td>匿名内部类</td>
<td>代码简洁</td>
<td>无法复用</td>
<td>一次性任务</td>
</tr>
</tbody>
</table>
<h2>线程常用方法</h2>
<h3>Thread.sleep(毫秒)</h3>
<p>让当前线程从运行状态变为休眠（阻塞）状态，时间到期后回到就绪状态。<strong>sleep 不会释放锁</strong>。</p>
<pre><code>try {
    Thread.sleep(1000);  // 休眠 1 秒
} catch (InterruptedException e) {
    e.printStackTrace();
}
</code></pre>
<h3>Thread.currentThread()</h3>
<p>获取当前正在执行的线程对象。</p>
<pre><code>System.out.println("id:" + Thread.currentThread().getId() + "-i:" + i);
</code></pre>
<h3>getId / getName / setName</h3>
<pre><code>Thread thread = new Thread(task);
thread.setName("线程①");
System.out.println("id:" + thread.getId() + " name:" + thread.getName());
</code></pre>
<p>线程 ID 是 JVM 自动分配的、不重复的 long 值。</p>
<h3>线程状态流转</h3>
<pre><code>新建(New) → 就绪(Runnable) → 运行(Running) → 阻塞(Blocked)
                              ↓                    ↓
                           终止(Dead)         就绪(Runnable)
</code></pre>
<h2>线程安全与同步</h2>
<h3>线程安全问题</h3>
<p>多个线程同时访问共享资源时，由于线程的随机切换和交错执行，导致数据不一致的问题。</p>
<p><strong>经典案例：火车票售票系统</strong></p>
<pre><code>class ThreadTrain1 implements Runnable {
    int trainCount = 100;  // 共享资源

    @Override
    public void run() {
        while (trainCount &gt; 0) {
            show();
        }
    }

    public void show() {
        if (trainCount &gt; 0) {
            try { Thread.sleep(10); } catch (Exception e) { }
            System.out.println("出售第" + (100 - trainCount + 1) + "票");
            trainCount--;
        }
    }
}
</code></pre>
<p>上述代码中，两个线程同时对 <code>trainCount</code> 进行读写操作，会出现线程安全问题（如：卖出第 0 张票、重复卖同一张票）。</p>
<h3>解决方案：synchronized 关键字</h3>
<p><code>synchronized</code> 保证同一时刻只有一个线程执行被修饰的代码块/方法，实现线程同步。</p>
<p><strong>方式一：同步代码块</strong></p>
<pre><code>public void show() {
    synchronized (oj) {  // 使用任意对象作为锁
        if (trainCount &gt; 0) {
            try { Thread.sleep(10); } catch (Exception e) { }
            System.out.println("出售第" + (100 - trainCount + 1) + "票");
            trainCount--;
        }
    }
}
</code></pre>
<p><strong>方式二：同步方法</strong></p>
<pre><code>public synchronized void show() {  // 默认锁是 this
    if (trainCount &gt; 0) {
        try { Thread.sleep(10); } catch (Exception e) { }
        System.out.println("出售第" + (100 - trainCount + 1) + "票");
        trainCount--;
    }
}
</code></pre>
<p><strong>方式三：静态同步方法</strong></p>
<pre><code>public static synchronized void show() {  // 锁是 类.class
    if (trainCount &gt; 0) {
        // ...
    }
}
</code></pre>
<h3>三种 synchronized 对比</h3>
<table>
<thead>
<tr>
<th>形式</th>
<th>锁对象</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>synchronized(oj)</code></td>
<td>任意对象 <code>oj</code></td>
<td>粒度最灵活</td>
</tr>
<tr>
<td><code>synchronized method</code></td>
<td><code>this</code>（当前实例）</td>
<td>整个方法加锁</td>
</tr>
<tr>
<td><code>static synchronized method</code></td>
<td><code>类名.class</code>（类对象）</td>
<td>类级别锁，所有实例共享</td>
</tr>
</tbody>
</table>
<h2>死锁</h2>
<h3>什么是死锁？</h3>
<p>死锁是指两个或两个以上的线程在执行过程中，<strong>互相持有对方需要的锁</strong>，导致所有线程都无法继续执行的状态。</p>
<h3>死锁产生的原因</h3>
<p>同步中嵌套了同步，即：</p>
<pre><code>线程1 持有锁 A，等待锁 B
线程2 持有锁 B，等待锁 A
         ↓
    互相等待，死锁形成
</code></pre>
<h3>死锁示例</h3>
<pre><code>public void run() {
    if (flag) {
        while (trainCount &gt; 0) {
            synchronized (oj) {      // 先拿 oj 锁
                show();              // 进入 show() 需要 this 锁
            }
        }
    } else {
        while (trainCount &gt; 0) {
            show();                  // 先拿 this 锁
            // show() 内需要 oj 锁
        }
    }
}

public synchronized void show() {    // this 锁
    synchronized (oj) {              // 再拿 oj 锁
        // 卖票...
    }
}
</code></pre>
<h3>如何避免死锁</h3>
<ul>
<li>避免同步嵌套</li>
<li>固定锁的获取顺序</li>
<li>使用 <code>tryLock()</code> 等超时机制</li>
</ul>
<h2>实战：多线程分批发送短信</h2>
<h3>业务场景</h3>
<p>向 140 名学员发送短信通知，要求使用多线程分批发送，提高效率。</p>
<h3>核心设计</h3>
<pre><code>用户数据 (140条)
    ↓
ListUtils.splitList(list, pageSize=50)
    ↓
[第1批: 50条] → UserThread 线程1
[第2批: 50条] → UserThread 线程2
[第3批: 40条] → UserThread 线程3
</code></pre>
<h3>工具类：List 分批切割</h3>
<pre><code>public class ListUtils {
    static public &lt;T&gt; List&lt;List&lt;T&gt;&gt; splitList(List&lt;T&gt; list, int pageSize) {
        int listSize = list.size();
        int page = (listSize + (pageSize - 1)) / pageSize;
        List&lt;List&lt;T&gt;&gt; listArray = new ArrayList&lt;List&lt;T&gt;&gt;();
        for (int i = 0; i &lt; page; i++) {
            List&lt;T&gt; subList = new ArrayList&lt;T&gt;();
            for (int j = 0; j &lt; listSize; j++) {
                int pageIndex = ((j + 1) + (pageSize - 1)) / pageSize;
                if (pageIndex == (i + 1)) {
                    subList.add(list.get(j));
                }
            }
            listArray.add(subList);
        }
        return listArray;
    }
}
</code></pre>
<h3>实体类</h3>
<pre><code>public class UserEntity {
    private String userId;
    private String userName;
    // getter / setter
}
</code></pre>
<h3>发送线程</h3>
<pre><code>class UserThread extends Thread {
    private List&lt;UserEntity&gt; list;

    public UserThread(List&lt;UserEntity&gt; list) {
        this.list = list;
    }

    public void run() {
        for (UserEntity userEntity : list) {
            System.out.println("threadName:" + Thread.currentThread().getName()
                + "-学员编号:" + userEntity.getUserId()
                + "---学员名称:" + userEntity.getUserName());
            // 调用发送短信具体代码
        }
    }
}
</code></pre>
<h3>主程序</h3>
<pre><code>public class SendThread {
    public static void main(String[] args) {
        List&lt;UserEntity&gt; listUserEntity = init();
        int userThreadPage = 50;
        List&lt;List&lt;UserEntity&gt;&gt; splitUserList = ListUtils.splitList(listUserEntity, userThreadPage);

        for (int i = 0; i &lt; splitUserList.size(); i++) {
            List&lt;UserEntity&gt; list = splitUserList.get(i);
            UserThread userThread = new UserThread(list);
            userThread.start();
        }
    }
}
</code></pre>
<h3>实战总结</h3>
<table>
<thead>
<tr>
<th>要点</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>数据分片</td>
<td>将大数据集拆分为小批次</td>
</tr>
<tr>
<td>多线程并行</td>
<td>每个批次独立线程处理</td>
</tr>
<tr>
<td>线程安全</td>
<td>每个线程只处理自己的数据，无需共享锁</td>
</tr>
<tr>
<td>扩展性</td>
<td>可调整 <code>pageSize</code> 控制并发度</td>
</tr>
</tbody>
</table>
<h2>面试题梳理</h2>
<h3>基础概念</h3>
<ol>
<li>
<p><strong>进程和线程的区别是什么？</strong> 进程是资源分配的最小单位，线程是 CPU 调度的最小单位。进程之间独立，线程共享进程资源。创建/销毁线程的开销远小于进程。</p>
</li>
<li>
<p><strong>Java 中创建线程有哪几种方式？</strong> 继承 <code>Thread</code> 类、实现 <code>Runnable</code> 接口、使用匿名内部类 / Lambda 表达式。</p>
</li>
<li>
<p><strong><code>start()</code> 和 <code>run()</code> 的区别？</strong> <code>start()</code> 启动一个新线程，由新线程执行 <code>run()</code>；<code>run()</code> 是普通方法调用，在主线程执行。</p>
</li>
</ol>
<h3>线程安全</h3>
<ol>
<li>
<p><strong>什么是线程安全问题？</strong> 多个线程并发访问共享资源，导致数据不一致。</p>
</li>
<li>
<p><strong><code>synchronized</code> 关键字的作用？</strong> 保证同一时刻只有一个线程执行被修饰的代码，保证可见性和原子性。</p>
</li>
<li>
<p><strong>同步代码块和同步方法的区别？</strong> 同步代码块锁对象可自定义，粒度更灵活；同步方法锁对象固定为 <code>this</code> 或 <code>类.class</code>。</p>
</li>
<li>
<p><strong>静态同步方法的锁是什么？</strong> <code>类名.class</code>（类级别的锁，所有实例共享）。</p>
</li>
</ol>
<h3>死锁</h3>
<ol>
<li>
<p><strong>什么是死锁？产生条件？</strong> 两个线程互相持有对方需要的锁，互相等待。四个必要条件：互斥、持有并等待、不可剥夺、循环等待。</p>
</li>
<li>
<p><strong>如何避免死锁？</strong> 避免锁嵌套，统一锁的获取顺序，使用超时机制（如 <code>Lock.tryLock()</code>）。</p>
</li>
</ol>
<h3>进阶</h3>
<ol>
<li>
<p><strong><code>sleep()</code> 和 <code>wait()</code> 的区别？</strong> <code>sleep()</code> 是 Thread 类方法，不释放锁；<code>wait()</code> 是 Object 类方法，释放锁，需在同步块中使用。</p>
</li>
<li>
<p><strong>多线程的优缺点？</strong> 优点：提高 CPU 利用率、提升响应速度；缺点：增加复杂度、可能出现死锁/线程安全问题。</p>
</li>
</ol>
<hr />
<p>参考资源：课程来源 [www.zxit8.com] 0001-多线程快速入门，作者余胜军（上海每特教育科技有限公司），JDK 版本 1.8。示例代码可直接运行，建议配合源码目录一起学习。</p>
]]></content>
        <author>
            <name>luhaodai</name>
            <uri>https://luhaodai.github.io/</uri>
        </author>
        <published>2026-07-12T00:00:00.000Z</published>
    </entry>
</feed>