发布于2026-07-04 阅读(0)
扫一扫,手机访问
在Ubuntu系统中玩Ja va多线程,核心逻辑其实和Windows、macOS并没有本质区别——底层依赖的还是Ja va语言自身的那套多线程机制,比如Thread类、Runnable接口,还有强大的ja va.util.concurrent包。不过,既然环境变成了Ubuntu,有些技巧和最佳实践还是值得单独拎出来聊聊的。

为什么?因为Ja va是单继承,一旦继承了Thread类,就没法再继承其他类,灵活性大打折扣。而实现Runnable接口就灵活多了,特别适合多个线程共享一份资源的场景。一个典型的例子是这样的:
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
}
}
}
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start();
}
}
手动创建和销毁线程的开销其实不小,尤其是在任务频繁的场景下。更优雅的做法是使用ExecutorService来统一管理线程的生命周期。在Ubuntu下,一个很实用的技巧是用Runtime.getRuntime().a vailableProcessors()获取CPU核心数,以此作为线程池大小的依据,从而最大化利用系统资源:
import ja va.util.concurrent.ExecutorService;
import ja va.util.concurrent.Executors;
public class Main {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().a vailableProcessors());
for (int i = 0; i < 10; i++) {
executor.submit(() -> System.out.println(Thread.currentThread().getName() + " is running"));
}
executor.shutdown();
}
}
这是最简单直接的同步方式。既可以锁方法,也可以锁代码块。关键原则是:同步范围能小就小,只锁住真正需要保护的共享数据。比如下面这个计数器例子:
class Counter {
private int count = 0;
public synchronized void increment() { // 同步方法
count++;
}
public int getCount() { return count; }
}
当你需要更精细的控制——比如公平锁、非公平锁切换,或者想尝试获取锁而不会被阻塞住——那么ReentrantLock就是好选择。不过有一点必须小心:锁一定要在finally块中释放,否则一旦出现异常,锁就永远解不开了。
import ja va.util.concurrent.locks.Lock;
import ja va.util.concurrent.locks.ReentrantLock;
class Counter {
private int count = 0;
private final Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
}
这种场景很常见:主线程需要等N个子线程都完事了,才能继续往下走。CountDownLatch就是为这个设计的,简单又高效:
import ja va.util.concurrent.CountDownLatch;
class Worker extends Thread {
private final CountDownLatch latch;
Worker(CountDownLatch latch) { this.latch = latch; }
@Override
public void run() {
try { Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + " done"); }
catch (InterruptedException e) { e.printStackTrace(); }
finally { latch.countDown(); }
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
int numWorkers = 3;
CountDownLatch latch = new CountDownLatch(numWorkers);
for (int i = 0; i < numWorkers; i++) new Worker(latch).start();
latch.await();
System.out.println("All workers done");
}
}
如果需要在多线程环境下操作Map,ConcurrentHashMap几乎是首选。它内部已经做好了分段锁或CAS优化,并发性能远优于手工加锁的Collections.synchronizedMap:
import ja va.util.concurrent.ConcurrentHashMap;
public class Main {
public static void main(String[] args) {
ConcurrentHashMap map = new ConcurrentHashMap<>();
map.put("key1", 1);
map.put("key2", 2);
System.out.println(map.get("key1"));
}
}
线程池的核心参数配置,归根结底要看任务类型。一个常见经验:
CPU核心数 + 1,目的是让CPU始终满载运转。2 * CPU核心数,因为大多数线程在等待I/O,CPU可以趁机处理其他任务。另外,拒绝策略也很关键。如果任务提交太快,线程池满了,队列也满了,怎么办?推荐使用 CallerRunsPolicy:让提交任务的线程自己去执行,这样既能缓解线程池压力,又不会丢掉任务。
死锁通常发生在“你等我,我等你”的嵌套锁场景。比如线程1持有锁A去请求锁B,而线程2持有锁B又在请求锁A,结果两边都block住了。一个很实用的避免策略是:统一锁顺序——所有线程都以相同的顺序获取锁,死锁问题就不攻自破。
// 统一锁顺序避免死锁
synchronized (lockA) {
synchronized (lockB) {
/* 业务逻辑 */
}
}
另一个容易被忽视的问题是同步范围过大。如果一个操作本身是线程安全的,只是修改共享数据那一步需要保护,那就只保护那一步,别把整个方法都锁住。同步的范围越小,系统的吞吐量就越高。
在Ubuntu上跑Ja va多线程程序,JVM参数是绕不开的优化手段。比如通过 -Xms 和 -Xmx 设置堆内存大小,避免频繁GC拖慢线程。垃圾回收方面,G1GC 是一个不错的通用选择,尤其适合多线程、大堆内存的场景:
ja va -Xms512m -Xmx1024m -XX:+UseG1GC -jar YourApp.jar
光靠猜肯定不行。在Ubuntu下,推荐使用 VisualVM 或 JProfiler 来实时监控线程状态——哪个线程在阻塞、哪个在等待、哪个在死循环,一目了然。性能瓶颈往往就在这些细节里。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8