Java 中的多线程是通过并发编程来提高应用程序的效率和响应速度。Java 提供了多个机制和类来支持多线程编程,包括继承 Thread
类、实现 Runnable
接口、使用线程池等。以下是 Java 中一些常见的多线程操作和应用场景。
1. 创建线程
1.1 通过继承 Thread
类创建线程
继承 Thread
类并重写 run
方法是创建线程的一种方式。run
方法包含线程的执行体。
public class MyThread extends Thread {@Overridepublic void run() {System.out.println("Thread is running: " + Thread.currentThread().getName());}public static void main(String[] args) {MyThread thread = new MyThread();thread.start(); // 启动线程}
}
start()
方法会启动线程并调用run()
方法。run()
方法不能直接调用,必须通过start()
启动线程。
1.2 通过实现 Runnable
接口创建线程
Runnable
接口适合当你不想继承 Thread
类时使用。你只需要实现 run
方法,然后将其传递给 Thread
对象。
public class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("Thread is running: " + Thread.currentThread().getName());}public static void main(String[] args) {MyRunnable myRunnable = new MyRunnable();Thread thread = new Thread(myRunnable);thread.start(); // 启动线程}
}
2. 线程的生命周期
线程有几个常见的生命周期状态:
- 新建(New):线程对象被创建,但未调用
start()
方法。 - 可运行(Runnable):线程被启动,处于可运行状态,但可能被操作系统调度暂停。
- 阻塞(Blocked):线程正在等待某个资源。
- 等待(Waiting):线程正在等待其他线程执行某些操作。
- 终止(Terminated):线程执行完毕,生命周期结束。
3. 线程的同步
当多个线程访问共享资源时,为了避免数据不一致的问题,通常需要使用同步机制。
3.1 使用 synchronized
关键字
synchronized
关键字用于在方法或代码块上加锁,确保在同一时间内只有一个线程能够执行被同步的代码块。
示例:同步方法
public class Counter {private int count = 0;// 使用 synchronized 修饰方法,保证线程安全public synchronized void increment() {count++;}public int getCount() {return count;}public static void main(String[] args) {Counter counter = new Counter();// 创建两个线程同时访问共享资源Thread t1 = new Thread(() -> {for (int i = 0; i < 1000; i++) {counter.increment();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 1000; i++) {counter.increment();}});t1.start();t2.start();try {t1.join(); // 等待线程 t1 执行完毕t2.join(); // 等待线程 t2 执行完毕} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Final count: " + counter.getCount()); // 输出: Final count: 2000}
}
在上面的例子中,increment
方法被 synchronized
修饰,这意味着同一时刻只有一个线程能够访问这个方法。
3.2 使用同步代码块
你也可以使用同步代码块来锁定指定的代码区域,从而减少锁的范围,提高效率。
public class Counter {private int count = 0;public void increment() {synchronized (this) { // 锁定当前对象count++;}}public int getCount() {return count;}public static void main(String[] args) {Counter counter = new Counter();Thread t1 = new Thread(() -> {for (int i = 0; i < 1000; i++) {counter.increment();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 1000; i++) {counter.increment();}});t1.start();t2.start();try {t1.join(); // 等待线程 t1 执行完毕t2.join(); // 等待线程 t2 执行完毕} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Final count: " + counter.getCount()); // 输出: Final count: 2000}
}
在上面的代码中,increment
方法内的同步代码块确保了每次只有一个线程能够进入该代码块,防止了竞态条件的出现。
4. 线程间通信
线程间通信是在多个线程之间交换信息的机制。Java 提供了 wait()
、notify()
和 notifyAll()
方法来实现线程间的通信。
4.1 使用 wait()
和 notify()
进行线程通信
wait()
:使当前线程进入等待状态,并释放锁,直到被其他线程通知。notify()
:通知一个正在等待的线程,使其从等待状态中醒来,继续执行。notifyAll()
:通知所有正在等待的线程,所有线程都会尝试重新获取锁。
示例:生产者-消费者问题
class Storage {private int product = 0;private final int capacity = 10;// 生产者生产产品public synchronized void produce() throws InterruptedException {while (product >= capacity) {wait(); // 如果库存已满,生产者等待}product++;System.out.println("Produced, product count: " + product);notifyAll(); // 通知消费者线程}// 消费者消费产品public synchronized void consume() throws InterruptedException {while (product <= 0) {wait(); // 如果库存为空,消费者等待}product--;System.out.println("Consumed, product count: " + product);notifyAll(); // 通知生产者线程}
}public class ProducerConsumer {public static void main(String[] args) {Storage storage = new Storage();// 生产者线程Thread producer = new Thread(() -> {try {for (int i = 0; i < 20; i++) {storage.produce();Thread.sleep(100); // 模拟生产时间}} catch (InterruptedException e) {e.printStackTrace();}});// 消费者线程Thread consumer = new Thread(() -> {try {for (int i = 0; i < 20; i++) {storage.consume();Thread.sleep(150); // 模拟消费时间}} catch (InterruptedException e) {e.printStackTrace();}});producer.start();consumer.start();}
}
在这个例子中,我们模拟了生产者和消费者的线程通信机制。生产者线程不断生产产品,而消费者线程不断消费产品。当产品库存满时,生产者等待;当产品库存为空时,消费者等待。
5. 线程池(Executor Service)
线程池是 Java 提供的一个高效的多线程管理工具,它可以避免创建过多的线程,减少系统资源的消耗。Java 中的线程池是通过 ExecutorService
接口来管理的。
5.1 创建线程池
线程池的创建通常使用 Executors
工厂类,它提供了几种常用的线程池:
newFixedThreadPool(int n)
:创建一个固定大小的线程池。
示例:使用 ExecutorService
执行任务
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ThreadPoolExample {public static void main(String[] args) {// 创建一个固定大小的线程池,池中有 3 个线程ExecutorService executorService = Executors.newFixedThreadPool(3);// 提交多个任务给线程池for (int i = 0; i < 5; i++) {executorService.submit(() -> {System.out.println("Task executed by: " + Thread.currentThread().getName());try {Thread.sleep(1000); // 模拟任务执行} catch (InterruptedException e) {e.printStackTrace();}});}// 关闭线程池executorService.shutdown(); // 提交完任务后调用 shutdown 来关闭线程池}
}
6. 线程的优先级
Java 允许设置线程的优先级,从而影响线程的调度顺序。线程的优先级是一个整数值,范围从 1 到 10,其中 1 为最低优先级,10 为最高优先级。
示例:设置线程优先级
public class ThreadPriority {public static void main(String[] args) {Thread highPriorityThread = new Thread(() -> {System.out.println("High priority thread is running.");});Thread lowPriorityThread = new Thread(() -> {System.out.println("Low priority thread is running.");});highPriorityThread.setPriority(Thread.MAX_PRIORITY); // 设置高优先级lowPriorityThread.setPriority(Thread.MIN_PRIORITY); // 设置低优先级highPriorityThread.start();lowPriorityThread.start();}
}
注意,虽然 Java 提供了线程优先级的设置,但线程调度是由操作系统管理的,不同的操作系统可能会根据自己的调度算法来决定线程的实际执行顺序。
7. 中断线程
线程的中断通常用于停止线程的执行或通知线程需要停止。通过调用线程的 interrupt()
方法可以设置线程的中断标志,而线程可以通过 isInterrupted()
方法来检查自己是否被中断。需要注意的是,interrupt()
并不会立即终止线程,它只是设置线程的中断状态,具体的中断行为需要在线程代码中自行判断并处理。
示例:中断线程
public class InterruptExample {public static void main(String[] args) throws InterruptedException {Thread longRunningThread = new Thread(() -> {try {for (int i = 0; i < 10; i++) {if (Thread.interrupted()) {System.out.println("Thread is interrupted, stopping...");return; // 响应中断,终止线程}System.out.println("Running... " + i);Thread.sleep(1000); // 模拟长时间任务}} catch (InterruptedException e) {System.out.println("Thread was interrupted during sleep.");}});longRunningThread.start();// 等待 3 秒后中断线程Thread.sleep(3000);longRunningThread.interrupt(); // 发出中断信号}
}
8. 线程的 join()
方法
join()
方法用于等待一个线程完成。当调用 join()
方法时,当前线程会阻塞,直到目标线程执行完毕为止。它常用于确保某些任务执行完之后再执行其他任务。
示例:使用 join()
等待线程完成
public class JoinExample {public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread(() -> {try {Thread.sleep(2000); // 模拟任务执行System.out.println("Thread 1 finished");} catch (InterruptedException e) {e.printStackTrace();}});Thread thread2 = new Thread(() -> {try {Thread.sleep(1000); // 模拟任务执行System.out.println("Thread 2 finished");} catch (InterruptedException e) {e.printStackTrace();}});thread1.start();thread2.start();// 等待 thread1 和 thread2 完成后再继续执行thread1.join();thread2.join();System.out.println("All threads finished");}
}
在这个例子中,main
线程会等待 thread1
和 thread2
执行完毕后再继续执行。