十分想念顺店杂可。。。

Java 多线程编程是 Java 并发编程的核心内容,它允许程序同时执行多个任务(线程),从而提高程序的执行效率和资源利用率。下面从基础概念、实现方式、生命周期、核心操作、同步机制、通信方式、线程池等方面详细介绍:

一、基本概念

  • 进程:操作系统资源分配的基本单位,一个运行中的程序就是一个进程(如打开的浏览器)。
  • 线程:进程内的执行单元,是 CPU 调度的基本单位。一个进程可以包含多个线程,线程共享进程的内存资源(如堆内存),但有自己独立的栈内存。
  • 多线程的优势
    • 提高 CPU 利用率(当一个线程阻塞时,其他线程可继续执行);
    • 提升程序响应速度(如 UI 程序中,后台任务不阻塞界面交互);
    • 便于处理并发任务(如服务器同时处理多个客户端请求)。

二、Java 实现多线程的两种核心方式

Java 中创建线程的核心方式有两种(本质都是基于Runnable接口):

1. 继承Thread

Thread类是 Java 中线程的基础类,它实现了Runnable接口。步骤:

  • 自定义类继承Thread
  • 重写run()方法(线程执行的核心逻辑);
  • 创建线程对象,调用start()方法启动线程(而非直接调用run()start()会触发 JVM 创建新线程并执行run())。

示例代码:

class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
            try {
                Thread.sleep(500); // 线程休眠500ms(模拟任务耗时)
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class ThreadDemo {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        t1.setName("线程1");
        t2.setName("线程2");
        t1.start(); // 启动线程1
        t2.start(); // 启动线程2
    }
}
2. 实现Runnable接口

由于 Java 是单继承(一个类只能继承一个父类),实现Runnable接口更灵活(可同时继承其他类)。步骤:

  • 自定义类实现Runnable接口;
  • 实现run()方法;
  • 创建Thread对象,将Runnable实例作为参数传入;
  • 调用Thread对象的start()方法启动线程。

示例代码:

class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class RunnableDemo {
    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        Thread t1 = new Thread(runnable, "线程A");
        Thread t2 = new Thread(runnable, "线程B");
        t1.start();
        t2.start();
    }
}

三、线程的生命周期

Java 线程有 5 种状态,状态转换是多线程编程的核心逻辑:

  1. 新建(New):线程对象创建后(未调用start())的状态。
  2. 就绪(Runnable):调用start()后,线程进入就绪队列,等待 CPU 调度(此时未真正执行)。
  3. 运行(Running):CPU 调度线程后,执行run()方法的状态。
  4. 阻塞(Blocked/Waiting/Timed Waiting):线程暂停执行,放弃 CPU 资源,直到满足条件后重新进入就绪状态。
    • 阻塞原因:sleep(long)(超时后自动唤醒)、wait()(需被notify()唤醒)、等待同步锁(synchronized竞争失败)等。
  5. 死亡(Terminated)run()方法执行完毕,或线程被stop()(已过时,可能导致资源未释放)终止。

四、线程的核心方法

方法作用
start()启动线程,将线程从 “新建” 转为 “就绪”(由 JVM 调用run())。
run()线程执行的核心逻辑(需重写)。
sleep(long ms)让当前线程休眠指定毫秒数(进入Timed Waiting状态,不释放锁)。
join()让当前线程等待调用者线程执行完毕(如t1.join():主线程等待 t1 结束)。
yield()让当前线程主动放弃 CPU 资源,重新进入就绪状态(礼让,不保证成功)。
interrupt()中断线程(设置中断标志,需配合isInterrupted()判断)。
isInterrupted()判断线程是否被中断(不清除中断标志)。
setDaemon(boolean)设置为守护线程(如垃圾回收线程),当所有非守护线程结束时自动终止。

五、线程同步(解决线程安全问题)

当多个线程共享资源(如全局变量)时,可能出现数据不一致(线程安全问题)。例如:两个线程同时对同一个变量做i++操作,可能导致结果错误(i++非原子操作,分 “读 - 改 - 写” 三步)。

解决方式:通过同步机制保证同一时间只有一个线程访问共享资源。

1. synchronized关键字(隐式锁)

synchronized可修饰方法或代码块,本质是通过 “对象锁” 保证同步:

  • 同步方法:锁对象是this(实例方法)或类对象(静态方法)。
  • 同步代码块:锁对象可自定义(更灵活,缩小同步范围)。

示例:解决i++线程安全问题

class Counter {
    private int count = 0;
    
    // 同步方法(锁对象为this)
    public synchronized void increment() {
        count++;
    }
    
    public int getCount() {
        return count;
    }
}

public class SyncDemo {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                counter.increment();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                counter.increment();
            }
        });
        t1.start();
        t2.start();
        t1.join(); // 等待t1结束
        t2.join(); // 等待t2结束
        System.out.println("最终结果:" + counter.getCount()); // 正确输出20000
    }
}
2. Lock接口(显式锁,JDK 1.5+)

java.util.concurrent.locks.Lock是更灵活的同步机制,需手动获取和释放锁(try-finally确保释放)。常用实现类ReentrantLock(可重入锁)。

示例:用Lock解决上述问题

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Counter {
    private int count = 0;
    private Lock lock = new ReentrantLock(); // 创建锁对象
    
    public void increment() {
        lock.lock(); // 获取锁
        try {
            count++;
        } finally {
            lock.unlock(); // 释放锁(必须在finally中,避免锁泄漏)
        }
    }
    
    public int getCount() {
        return count;
    }
}

Lock vs synchronized

  • Lock需手动释放锁,synchronized自动释放;
  • Lock可尝试获取锁(tryLock()),避免死锁;
  • Lock可实现公平锁(按等待顺序获取锁),synchronized是非公平锁。

六、线程通信(线程间协作)

多个线程通过共享资源协作时,需通过通信机制协调执行顺序(如 “生产者 - 消费者” 模型)。核心方法:wait()notify()notifyAll()(需在synchronized块中使用)。

  • wait():让当前线程释放锁,进入等待队列(Waiting状态),直到被notify()唤醒。
  • notify():随机唤醒等待队列中的一个线程。
  • notifyAll():唤醒等待队列中的所有线程。

示例:生产者 - 消费者模型(生产一个,消费一个)

class SharedResource {
    private int data;
    private boolean hasData = false; // 标记是否有数据可供消费
    
    // 生产数据
    public synchronized void produce(int newData) throws InterruptedException {
        while (hasData) { // 若已有数据,生产者等待
            wait();
        }
        data = newData;
        hasData = true;
        System.out.println("生产:" + data);
        notify(); // 唤醒消费者
    }
    
    // 消费数据
    public synchronized int consume() throws InterruptedException {
        while (!hasData) { // 若无数据,消费者等待
            wait();
        }
        hasData = false;
        System.out.println("消费:" + data);
        notify(); // 唤醒生产者
        return data;
    }
}

public class ProducerConsumer {
    public static void main(String[] args) {
        SharedResource resource = new SharedResource();
        
        // 生产者线程
        Thread producer = new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                try {
                    resource.produce(i);
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        
        // 消费者线程
        Thread consumer = new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                try {
                    resource.consume();
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        
        producer.start();
        consumer.start();
    }
}

七、线程池(Executor框架)

频繁创建 / 销毁线程会消耗大量资源,线程池可复用线程,降低开销。JDK 通过java.util.concurrent.Executor框架提供线程池支持,核心类ThreadPoolExecutor

1. 常用线程池类型(通过Executors工具类创建)
线程池类型特点适用场景
FixedThreadPool固定大小的线程池,核心线程数 = 最大线程数。任务数量固定的场景
CachedThreadPool可缓存的线程池,无核心线程,最大线程数不限(按需创建,超时回收)。短期、轻量任务
ScheduledThreadPool支持定时 / 周期性任务的线程池。定时任务(如定时备份)
SingleThreadExecutor单线程池,所有任务按顺序执行。需要顺序执行的任务
2. 线程池使用示例
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolDemo {
    public static void main(String[] args) {
        // 创建固定大小为3的线程池
        ExecutorService executor = Executors.newFixedThreadPool(3);
        
        // 提交5个任务
        for (int i = 0; i < 5; i++) {
            int taskId = i;
            executor.submit(() -> { // 提交任务(Runnable)
                System.out.println("任务" + taskId + "由线程" + Thread.currentThread().getName() + "执行");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        
        executor.shutdown(); // 关闭线程池(不再接受新任务,等待已有任务完成)
    }
}

八、高级主题

  1. 线程安全集合java.util.concurrent包下的集合(如ConcurrentHashMapCopyOnWriteArrayList),内部实现了线程安全,无需手动同步。
  2. ThreadLocal:为每个线程提供独立的变量副本,避免线程安全问题(如SimpleDateFormat非线程安全,可用ThreadLocal包装)。
  3. 原子类java.util.concurrent.atomic包下的类(如AtomicInteger),通过 CAS(Compare-And-Swap)机制实现无锁线程安全操作。
  4. 死锁:多个线程互相等待对方释放锁导致的无限阻塞。避免方式:按固定顺序获取锁、设置超时时间(tryLock())等。

总结

Java 多线程编程的核心是线程创建、状态管理、同步机制、线程通信和线程池。合理使用多线程可提升程序性能,但需注意线程安全问题(通过synchronizedLock解决),并避免死锁、资源泄漏等问题。实际开发中,优先使用线程池而非手动创建线程,以提高资源利用率。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐