Flyinsky's Codes
1109 字
6 分钟
多线程编程
2024-10-18

Java 多线程编程详解#

在 Java 中,多线程编程是一种允许多个线程并发执行任务的机制。多线程编程可以提高程序的效率,特别是在处理大量计算或 I/O 操作时。

一、多线程基础概念#

1. 线程和进程#
  • 进程:是程序在操作系统中执行的一个实例,具有独立的内存空间。
  • 线程:是进程的一个执行单元,一个进程可以有多个线程,它们共享进程的内存空间。
2. 多线程的优势#
  • 提高CPU利用率:多线程可以让 CPU 在等待 I/O 操作时继续执行其他线程的任务。
  • 改善程序的响应性:在 GUI 程序中,多线程可以防止用户界面卡顿。
  • 提高计算密集型任务的性能:可以并行执行多个计算任务,特别是在多核处理器上。
3. Java 中的线程状态#

Java 线程有 6 种主要状态:

  1. New:线程对象被创建,但还未启动。
  2. Runnable:线程准备运行,并等待 CPU 调度。
  3. Blocked:线程在等待锁释放。
  4. Waiting:线程在等待另一个线程执行特定的动作。
  5. Timed Waiting:线程在等待指定的时间。
  6. Terminated:线程完成了任务或因异常而终止。

二、Java 中创建多线程的三种方式#

1. 继承 Thread#

这是创建线程的最简单方法,直接继承 Thread 类并重写 run() 方法。

class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " - " + i);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        t1.start();  // 启动线程
        t2.start();
    }
}
优点:#
  • 简单直观。
缺点:#
  • Java 只支持单继承,继承 Thread 后无法继承其他类。

2. 实现 Runnable 接口#

相比继承 Thread 类,实现 Runnable 接口更加灵活,因为可以实现多个接口。

class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " - " + i);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread t1 = new Thread(myRunnable);
        Thread t2 = new Thread(myRunnable);
        t1.start();  // 启动线程
        t2.start();
    }
}
优点:#
  • 灵活,可以继承其他类。
  • 更适合资源共享。
缺点:#
  • 稍微复杂,需要创建 Thread 实例。

3. 使用 CallableFuture#

Callable 接口与 Runnable 类似,但它可以返回结果或抛出异常。使用 ExecutorService 执行并获取线程的返回值。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 5; i++) {
            sum += i;
            System.out.println(Thread.currentThread().getName() + " - Sum: " + sum);
        }
        return sum;
    }
}

public class Main {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(2);
        Future<Integer> result1 = executor.submit(new MyCallable());
        Future<Integer> result2 = executor.submit(new MyCallable());

        try {
            System.out.println("Result 1: " + result1.get());
            System.out.println("Result 2: " + result2.get());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        } finally {
            executor.shutdown();
        }
    }
}
优点:#
  • 可以返回结果。
  • 可以抛出异常。
缺点:#
  • 需要使用 ExecutorService 来管理线程。

三、线程同步#

当多个线程同时访问共享资源时,可能会发生数据不一致的问题。为了避免这种情况,Java 提供了线程同步机制。

1. synchronized 关键字#

synchronized 可以用于方法或代码块,确保同一时间只有一个线程可以访问某个方法或代码块。

同步方法:

class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
        System.out.println(Thread.currentThread().getName() + " - Count: " + count);
    }
}

public class Main {
    public static void main(String[] args) {
        Counter counter = new Counter();

        Runnable task = () -> {
            for (int i = 0; i < 5; i++) {
                counter.increment();
            }
        };

        Thread t1 = new Thread(task);
        Thread t2 = new Thread(task);
        t1.start();
        t2.start();
    }
}

2. Lock 接口#

Lock 接口提供了比 synchronized 更加灵活的锁定机制。

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++;
            System.out.println(Thread.currentThread().getName() + " - Count: " + count);
        } finally {
            lock.unlock();
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Counter counter = new Counter();

        Runnable task = () -> {
            for (int i = 0; i < 5; i++) {
                counter.increment();
            }
        };

        Thread t1 = new Thread(task);
        Thread t2 = new Thread(task);
        t1.start();
        t2.start();
    }
}

四、线程池#

创建大量线程会消耗系统资源。Java 提供了 ExecutorService 来管理线程池,从而更高效地复用线程。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(3);

        for (int i = 0; i < 5; i++) {
            executor.execute(() -> {
                System.out.println(Thread.currentThread().getName() + " - Task");
            });
        }

        executor.shutdown();
    }
}