java多线程提高效率的核心在于合理利用多核cpu和i/o并发,而非盲目创建线程;2. 应优先使用executorservice线程池而非直接new thread,以减少资源开销;3. 多线程适用场景为i/o密集型和可并行化的cpu密集型任务,需评估并行潜力;4. 必须通过synchronized、lock、volatile或原子类等机制避免竞态条件、死锁、可见性和有序性问题;5. 线程池需正确配置参数并调用shutdown()优雅关闭,防止资源泄漏。

Java代码要实现多线程以提高效率,核心在于合理利用多核CPU资源和处理I/O密集型任务的并发性。这不仅仅是简单地创建多个线程,更关乎如何有效管理它们、避免资源争抢,以及确保程序的正确性与稳定性。它是一种系统级的优化策略,而非单纯的代码行数堆叠。
在Java中编写多线程程序,提高效率的起点是理解并运用
Thread
Runnable
ExecutorService
最基础的方式是实现
Runnable
Thread
Runnable
立即学习“Java免费学习笔记(深入)”;
// 方式一:实现Runnable接口
class MyRunnable implements Runnable {
private String taskName;
public MyRunnable(String taskName) {
this.taskName = taskName;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 正在执行任务: " + taskName);
try {
// 模拟耗时操作
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 重新设置中断状态
System.out.println(Thread.currentThread().getName() + " 的任务被中断了。");
}
System.out.println(Thread.currentThread().getName() + " 完成任务: " + taskName);
}
}
// 方式二:继承Thread类 (不推荐,但了解一下)
class MyThread extends Thread {
private String taskName;
public MyThread(String taskName) {
super(taskName); // 设置线程名称
this.taskName = taskName;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 正在执行任务: " + taskName);
// ... 同上模拟耗时操作
}
}
public class MultiThreadExample {
public static void main(String[] args) {
// 启动Runnable任务
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(new MyRunnable("任务-" + i));
thread.start(); // 启动线程
}
// 启动Thread子类任务 (不推荐)
// new MyThread("独立线程任务").start();
}
}然而,直接创建和管理
Thread
java.util.concurrent
ExecutorService
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.Callable;
// 假设我们有一个需要返回结果的任务
class MyCallable implements Callable<String> {
private String taskName;
public MyCallable(String taskName) {
this.taskName = taskName;
}
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName() + " 正在执行可返回结果的任务: " + taskName);
Thread.sleep(200); // 模拟耗时
return "任务 " + taskName + " 完成,结果是:成功!";
}
}
public class ExecutorServiceExample {
public static void main(String[] args) {
// 创建一个固定大小的线程池,通常推荐根据CPU核心数和任务类型来设定
// 这里使用Executors工厂方法简化创建,实际项目中可能需要自定义ThreadPoolExecutor
ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
// 提交Runnable任务
for (int i = 0; i < 3; i++) {
executor.submit(new MyRunnable("Runnable任务-" + i));
}
// 提交Callable任务并获取Future
try {
Future<String> future1 = executor.submit(new MyCallable("Callable任务-A"));
Future<String> future2 = executor.submit(new MyCallable("Callable任务-B"));
// 可以阻塞等待结果,或者稍后通过future.isDone()检查
System.out.println("获取Callable任务-A的结果: " + future1.get());
System.out.println("获取Callable任务-B的结果: " + future2.get());
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭线程池,不再接受新任务,并等待已提交任务完成
executor.shutdown();
// 优雅关闭,等待所有任务执行完毕,或超时
try {
if (!executor.awaitTermination(60, java.util.concurrent.TimeUnit.SECONDS)) {
executor.shutdownNow(); // 如果超时,强制关闭
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
}很多人一提到多线程,就觉得是性能的银弹,只要把任务拆开扔给不同的线程,程序就能飞起来。但实际情况远非如此简单。我见过不少项目,盲目引入多线程后,性能不升反降,甚至稳定性都成了问题。这是因为多线程本身引入了额外的开销和复杂度。
首先,线程的创建和销毁本身就需要系统资源和时间。如果你频繁地创建和销毁大量短生命周期的线程,这部分开销可能比任务执行本身还要大。其次,线程调度和上下文切换也是有成本的。操作系统需要在不同的线程之间切换CPU的执行权,每次切换都需要保存当前线程的状态,加载下一个线程的状态,这都是CPU周期。当线程数量远超CPU核心数时,这种切换会变得非常频繁,导致“假性并发”,反而降低了效率。
再者,并发编程最大的挑战在于数据共享和同步。当多个线程同时访问和修改同一个数据时,如果没有正确的同步机制,就会出现竞态条件(Race Condition),导致数据不一致甚至程序崩溃。为了避免这些问题,我们需要使用
synchronized
Lock
那么,多线程到底什么时候才能真正提升效率呢?我的经验是,它主要适用于以下两种场景:
I/O密集型任务:这类任务的特点是大部分时间都在等待外部资源(如网络请求、磁盘读写、数据库查询)的响应。当一个线程在等待I/O时,CPU是空闲的。通过多线程,我们可以在一个线程等待I/O的同时,让另一个线程去执行计算任务或发起另一个I/O请求,从而充分利用CPU,提高整体吞吐量。
CPU密集型任务:这类任务需要大量的计算,并且任务本身可以被分解成独立的、可以并行执行的子任务。例如,图像处理、大数据分析、复杂算法计算等。在多核CPU上,每个核心可以同时执行一个线程,这样就可以真正实现并行计算,显著缩短总的执行时间。但这里有个大前提:任务必须是可并行化的,并且并行部分的比例要足够高。Amdahl定律告诉我们,程序的串行部分将限制并行化带来的最大加速比。如果你的程序大部分是串行执行的,那么无论你用多少个线程,性能提升都是有限的。
所以,在决定使用多线程之前,务必先分析你的任务类型,并评估并行化的潜力。别盲目上马,否则可能得不偿失。
在Java多线程编程中,直接创建
Thread
ExecutorService
Executors
ExecutorService
Executors.newFixedThreadPool(int nThreads)
Executors.newCachedThreadPool()
Executors.newSingleThreadExecutor()
Executors.newScheduledThreadPool(int corePoolSize)
在实际项目中,我更倾向于直接使用
ThreadPoolExecutor
import java.util.concurrent.*;
public class CustomThreadPoolExample {
public static void main(String[] args) {
// 核心线程数:即使空闲,也保留的线程数
int corePoolSize = 2;
// 最大线程数:线程池允许存在的最大线程数
int maximumPoolSize = 5;
// 线程空闲时间:当线程数超过核心线程数时,多余的空闲线程在终止前等待新任务的最长时间
long keepAliveTime = 60;
TimeUnit unit = TimeUnit.SECONDS;
// 任务队列:用于保存等待执行的任务的阻塞队列
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(100); // 设置队列容量
// 线程工厂:用于创建新线程
ThreadFactory threadFactory = r -> {
Thread t = new Thread(r, "CustomPool-Thread-" + r.hashCode());
System.out.println("创建新线程: " + t.getName());
return t;
};
// 拒绝策略:当任务队列已满且达到最大线程数时,如何处理新提交的任务
// AbortPolicy: 抛出RejectedExecutionException
// CallerRunsPolicy: 调用者线程执行任务
// DiscardPolicy: 直接丢弃任务
// DiscardOldestPolicy: 丢弃队列中最老的任务
RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue,
threadFactory,
handler
);
// 提交任务
for (int i = 0; i < 10; i++) {
final int taskId = i;
executor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 正在执行任务 " + taskId);
try {
Thread.sleep(500); // 模拟耗时
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
// 优雅关闭线程池
executor.shutdown(); // 不再接受新任务,但会执行已提交的任务
try {
// 等待所有任务执行完毕,最多等待60秒
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
System.out.println("线程池未能在指定时间内关闭,尝试强制关闭...");
executor.shutdownNow(); // 强制关闭,中断正在执行的任务
}
} catch (InterruptedException e) {
System.out.println("等待线程池关闭时被中断。");
executor.shutdownNow();
Thread.currentThread().interrupt();
}
System.out.println("线程池已关闭。");
}
}管理线程池,尤其重要的是要确保在程序结束时调用
executor.shutdown()
executor.awaitTermination()
用eclipse开发android程序的时,跟VS一样是可以断点单步调试的。 Eclipse Java编辑器不但能够为开发者提供代码编写、语法纠错和实时编译等常用功能,而且还能够对Java源代码进行快速修改、重构等高级操作。感兴趣的朋友可以过来看看
0
多线程编程的魅力在于它能提升效率,但其复杂性也往往让人望而却步,尤其是那些隐藏在代码深处的并发问题。这些问题一旦出现,轻则数据不一致,重则程序崩溃,而且往往难以复现和调试。在我看来,理解并避免这些“坑”比单纯学会如何创建线程要重要得多。
这是最常见的并发问题。当多个线程尝试同时访问和修改同一个共享资源时,最终结果的正确性取决于线程执行的相对顺序,而这个顺序是不可预测的。
示例: 多个线程对同一个计数器进行
i++
i++
避免策略:
同步机制: 使用
synchronized
java.util.concurrent.locks.Lock
// 使用synchronized
private int count = 0;
public synchronized void increment() {
count++;
}
// 使用ReentrantLock
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
private int countLock = 0;
private final Lock lock = new ReentrantLock();
public void incrementWithLock() {
lock.lock(); // 获取锁
try {
countLock++;
} finally {
lock.unlock(); // 释放锁
}
}原子操作类: 对于简单的数值操作,可以使用
java.util.concurrent.atomic
AtomicInteger
AtomicLong
synchronized
import java.util.concurrent.atomic.AtomicInteger;
private AtomicInteger atomicCount = new AtomicInteger(0);
public void incrementAtomic() {
atomicCount.incrementAndGet();
}线程封闭 (Thread Confinement): 将数据封装在线程内部,不与其他线程共享。例如,使用
ThreadLocal
不变性 (Immutability): 如果一个对象在创建后就不能被修改,那么它就是线程安全的。尽可能使用不可变对象。
当两个或多个线程在执行过程中,因争夺资源而造成互相等待的现象,如果没有外力干涉,它们将永远无法推进。死锁发生的四个必要条件:
避免策略: 破坏死锁的任意一个必要条件即可。最常见且有效的方法是破坏循环等待条件,即规定所有线程获取资源的顺序。
// 示例:经典的哲学家就餐问题简化版
// 假设有两把锁 A 和 B
Object lockA = new Object();
Object lockB = new Object();
// 线程1:先获取A,再获取B
new Thread(() -> {
synchronized (lockA) {
System.out.println("Thread 1: Got Lock A");
try { Thread.sleep(10); } catch (InterruptedException e) {} // 模拟耗时,增加死锁几率
synchronized (lockB) {
System.out.println("Thread 1: Got Lock B and finished.");
}
}
}).start();
// 线程2:先获取B,再获取A (可能导致死锁)
new Thread(() -> {
synchronized (lockB) { // 如果这里改为先获取A,则可以避免死锁
System.out.println("Thread 2: Got Lock B");
try { Thread.sleep(10); } catch (InterruptedException e) {}
synchronized (lockA) {
System.out.println("Thread 2: Got Lock A and finished.");
}
}
}).start();改进: 确保所有线程都以相同的顺序获取锁,例如,所有线程都先获取
lockA
lockB
当一个线程修改了共享变量的值,另一个线程可能无法立即看到这个修改。这通常发生在多核处理器架构下,每个核心有自己的缓存,变量的修改可能只写入了当前核心的缓存,而没有立即同步到主内存。
避免策略:
volatile
volatile
public volatile boolean running = true; // 当一个线程修改running,其他线程能立即看到
synchronized
Lock
final
final
编译器和处理器为了优化性能,可能会对指令进行重排序。虽然重排序在单线程环境下不会改变程序的结果,但在多线程环境下,这种重排序可能导致意想不到的行为。
避免策略:
volatile
volatile
synchronized
Lock
happens-before
happens-before
volatile
happens-before
并发编程没有捷径,理解Java内存模型、同步机制以及各种并发工具的底层原理至关
以上就是java代码怎样编写多线程程序提高效率 java代码多线程实现的实用教程的详细内容,更多请关注php中文网其它相关文章!
java怎么学习?java怎么入门?java在哪学?java怎么学才快?不用担心,这里为大家提供了java速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号