服务器测评网
我们一直在努力

Java多线程如何编写?新手入门步骤与代码示例详解

线程是Java并发编程的核心概念,它允许程序在同一个进程中执行多个任务,从而提高CPU利用率和程序响应速度,理解如何在Java中正确创建、管理和同步线程,是开发高性能、高并发应用的基础,本文将从线程的基础概念出发,详细讲解Java中线程的创建方式、生命周期、常用方法、同步机制以及线程池的使用,帮助读者全面掌握Java线程编程。

Java多线程如何编写?新手入门步骤与代码示例详解

线程的基础概念

线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位,一个进程可以包含多个线程,这些线程共享进程的内存空间和系统资源,但每个线程拥有独立的程序计数器、栈和局部变量,与进程相比,线程的创建和销毁开销更小,切换效率更高,因此适合处理高并发任务。

在Java中,线程的调度由Java虚拟机(JVM)和操作系统共同完成,JVM通过线程调度器分配CPU时间片,线程通过竞争时间片来执行任务,Java中的线程实现了java.lang.Thread类,开发者可以通过继承或实现接口来创建自定义线程。

Java中创建线程的三种方式

Java提供了多种创建线程的方式,每种方式都有其适用场景和优缺点,开发者可以根据需求选择合适的方法。

继承Thread类

最直接的创建线程方式是继承Thread类并重写其run()方法。run()方法中定义了线程要执行的任务,通过调用线程的start()方法启动线程。

示例代码:

class MyThread extends Thread {  
    @Override  
    public void run() {  
        for (int i = 0; i < 5; i++) {  
            System.out.println(Thread.currentThread().getName() + ": " + i);  
        }  
    }  
}  
public class ThreadDemo {  
    public static void main(String[] args) {  
        MyThread thread = new MyThread();  
        thread.start(); // 启动线程  
    }  
}  

优缺点:

  • 优点:实现简单,直接使用this即可访问当前线程对象。
  • 缺点:Java单继承限制,若类已继承其他父类则无法再继承Thread类;线程与任务耦合,不利于代码复用。

实现Runnable接口

更灵活的方式是实现Runnable接口,将线程任务与线程本身分离。Runnable接口只有一个run()方法,实现该接口后,将实例传递给Thread构造函数即可创建线程。

示例代码:

class MyRunnable implements Runnable {  
    @Override  
    public void run() {  
        for (int i = 0; i < 5; i++) {  
            System.out.println(Thread.currentThread().getName() + ": " + i);  
        }  
    }  
}  
public class RunnableDemo {  
    public static void main(String[] args) {  
        Runnable task = new MyRunnable();  
        Thread thread = new Thread(task);  
        thread.start();  
    }  
}  

优缺点:

  • 优点:避免单继承限制,线程任务可独立于线程类,实现“任务与执行分离”;多个线程可共享同一个Runnable实例,适合处理共享资源场景。
  • 缺点:Runnable接口没有返回值,无法直接获取线程执行结果。

实现Callable接口

当需要获取线程执行结果时,可以使用Callable接口。Callable接口是Java 5引入的,类似于Runnable,但其call()方法支持返回结果和抛出异常。

Java多线程如何编写?新手入门步骤与代码示例详解

示例代码:

import java.util.concurrent.Callable;  
import java.util.concurrent.FutureTask;  
class MyCallable implements Callable<Integer> {  
    @Override  
    public Integer call() throws Exception {  
        int sum = 0;  
        for (int i = 1; i <= 5; i++) {  
            sum += i;  
        }  
        return sum;  
    }  
}  
public class CallableDemo {  
    public static void main(String[] args) throws Exception {  
        Callable<Integer> task = new MyCallable();  
        FutureTask<Integer> futureTask = new FutureTask<>(task);  
        Thread thread = new Thread(futureTask);  
        thread.start();  
        // 获取线程执行结果(会阻塞,直到线程完成)  
        Integer result = futureTask.get();  
        System.out.println("计算结果: " + result);  
    }  
}  

优缺点:

  • 优点:支持返回结果,可抛出异常,适合需要线程返回值的场景。
  • 缺点:使用相对复杂,需配合FutureTask类获取结果。

线程的生命周期与状态

Java线程的生命周期包括多个状态,通过Thread.State枚举定义,共有6种状态:

  1. NEW(新建):线程被创建但未启动,调用start()方法之前的状态。
  2. RUNNABLE(可运行):线程已启动,正在等待CPU时间片或正在执行,Java将操作系统层面的“就绪”和“运行”状态统一为RUNNABLE
  3. BLOCKED(阻塞):线程等待获取锁(如synchronized锁),进入阻塞状态。
  4. WAITING(等待):线程等待其他线程显式唤醒(如调用wait()join()LockSupport.park())。
  5. TIMED_WAITING(超时等待):等待指定时间后自动唤醒(如调用sleep(long millis)wait(long timeout))。
  6. TERMINATED(终止):线程执行完成或因异常退出,生命周期结束。

状态转换由JVM和操作系统自动管理,开发者可通过Thread.getState()方法获取线程当前状态。

线程的常用方法

线程的常用方法用于控制线程的行为,以下是核心方法的说明:

  • start():启动线程,JVM会调用线程的run()方法,注意:直接调用run()方法不会创建新线程,仅在当前线程执行任务。
  • run():线程执行的任务方法,需由开发者重写(继承Thread或实现Runnable/Callable时)。
  • sleep(long millis):让当前线程休眠指定毫秒数,进入TIMED_WAITING状态,不释放锁。
  • yield():让当前线程主动让出CPU时间片,进入RUNNABLE状态,让同优先级线程有机会执行。
  • join():等待当前线程执行完成后再执行后续代码。thread.join()会使主线程阻塞,直到thread线程结束。
  • interrupt():中断线程,通过设置线程的中断标志实现,若线程处于阻塞状态(如sleep()),会抛出InterruptedException并清除中断标志。
  • currentThread():静态方法,返回当前正在执行的线程对象。

线程同步机制

多线程环境下,共享资源的访问可能导致数据不一致问题(如脏读、丢失更新),Java提供了多种同步机制来保证线程安全:

synchronized关键字

synchronized是Java内置的同步机制,通过锁机制实现互斥访问,它可以修饰方法、代码块或静态方法:

  • 修饰实例方法:锁为当前对象实例(this),同一时间只有一个线程能访问该对象的同步方法。
  • 修饰静态方法:锁为当前类的Class对象,同一时间只有一个线程能访问该类的所有同步静态方法。
  • 修饰代码块:可指定锁对象,灵活性更高,可缩小同步范围,减少锁竞争。

示例(同步代码块):

class Counter {  
    private int count = 0;  
    private final Object lock = new Object(); // 锁对象  
    public void increment() {  
        synchronized (lock) { // 同步代码块  
            count++;  
        }  
    }  
}  

Lock接口

java.util.concurrent.locks.Lock接口提供了更灵活的锁机制,支持公平锁、非公平锁、可中断锁等,常用实现类为ReentrantLock,它比synchronized更强大,但需手动释放锁(通过unlock()方法)。

示例:

Java多线程如何编写?新手入门步骤与代码示例详解

import java.util.concurrent.locks.ReentrantLock;  
class Counter {  
    private int count = 0;  
    private final ReentrantLock lock = new ReentrantLock();  
    public void increment() {  
        lock.lock(); // 获取锁  
        try {  
            count++;  
        } finally {  
            lock.unlock(); // 释放锁(必须放在finally中)  
        }  
    }  
}  

volatile关键字

volatile用于修饰变量,确保多线程环境下变量的可见性(一个线程修改后,其他线程立即可见)和禁止指令重排序,但它不保证原子性,仅适用于“一个线程写,多个线程读”的场景。

示例:

class Flag {  
    private volatile boolean isRunning = true; // 修改后对其他线程可见  
    public void stop() {  
        isRunning = false;  
    }  
    public void run() {  
        while (isRunning) {  
            // 执行任务  
        }  
    }  
}  

原子类

java.util.concurrent.atomic包下的原子类(如AtomicIntegerAtomicLong)通过CAS(Compare-And-Swap)操作实现原子性,适合高并发场景下的简单数据操作。

示例:

import java.util.concurrent.atomic.AtomicInteger;  
class Counter {  
    private AtomicInteger count = new AtomicInteger(0);  
    public void increment() {  
        count.incrementAndGet(); // 原子递增  
    }  
}  

线程池的使用

频繁创建和销毁线程会带来性能开销,线程池通过复用线程、管理线程数量,有效解决了这一问题,Java提供了java.util.concurrent.Executor框架,核心类为ThreadPoolExecutor

ThreadPoolExecutor构造参数

ThreadPoolExecutor的构造方法包含7个核心参数:

  • corePoolSize:核心线程数,线程池长期保持的线程数量。
  • maximumPoolSize:最大线程数,线程池允许的最大线程数。
  • keepAliveTime:空闲线程存活时间,超过该时间的空闲线程会被回收(核心线程除外)。
  • unit:存活时间单位(如TimeUnit.SECONDS)。
  • workQueue:工作队列,用于存储待执行任务(常用ArrayBlockingQueueLinkedBlockingQueueSynchronousQueue)。
  • threadFactory:线程工厂,用于创建线程(可自定义线程名称、优先级等)。
  • handler:拒绝策略,当线程数和队列都满时,对新任务的处理方式(如AbortPolicy抛出异常、CallerRunsPolicy由调用线程执行)。

创建线程池的方式

推荐通过ThreadPoolExecutor手动创建线程池,避免使用Executors工具类(如newFixedThreadPoolnewCachedThreadPool可能因队列无界导致OOM)。

示例:

import java.util.concurrent.ArrayBlockingQueue;  
import java.util.concurrent.ThreadPoolExecutor;  
import java.util.concurrent.TimeUnit;  
public class ThreadPoolDemo {  
    public static void main(String[] args) {  
        ThreadPoolExecutor pool = new ThreadPoolExecutor(  
                5, // 核心线程数  
                10, // 最大线程数  
                60L, // 空闲线程存活时间  
                TimeUnit.SECONDS, // 时间单位  
                new ArrayBlockingQueue<>(100), // 工作队列(容量100)  
                new ThreadPoolExecutor.AbortPolicy() // 拒绝策略  
        );  
        for (int i = 0; i < 10; i++) {  
            pool.execute(() -> {  
                System.out.println(Thread.currentThread().getName() + " is running");  
            });  
        }  
        pool.shutdown(); // 关闭线程池  
    }  
}  

线程编程最佳实践

  1. 避免锁竞争:尽量缩小同步范围,使用细粒度锁或无锁数据结构(如ConcurrentHashMap)。
  2. 合理设置线程池参数:根据任务类型(CPU密集型、IO密集型)调整核心线程数和工作队列。
  3. 处理线程异常:线程中的异常不会抛出到调用方,需通过Thread.setUncaughtExceptionHandler捕获处理。
  4. 避免阻塞操作:线程中尽量不执行耗时IO操作,可使用异步任务或专用IO线程池。

Java线程编程是并发开发的基础,掌握线程的创建、生命周期、同步机制和线程池使用,能够帮助开发者编写高效、稳定的并发程序,在实际开发中,需根据场景选择合适的线程创建方式,合理使用同步机制保证线程安全,并通过线程池优化资源利用,注意避免常见的并发问题(如死锁、内存泄漏),遵循最佳实践,才能充分发挥多线程的优势。

赞(0)
未经允许不得转载:好主机测评网 » Java多线程如何编写?新手入门步骤与代码示例详解