When to Choose Coroutines Over Threads: A Deep Dive into Java/Kotlin Concurrency
This article compares coroutines and threads for high‑concurrency and I/O‑intensive workloads versus CPU‑bound tasks, discusses Kotlin coroutine adoption, presents multithreading fundamentals, highlights performance pitfalls like System.currentTimeMillis, and offers code examples and optimization strategies for backend developers.
Coroutines have clear advantages for handling high concurrency and I/O‑intensive tasks, making asynchronous code simpler and more efficient. For CPU‑bound workloads and traditional multitasking, threads remain a mature and effective choice.
The decision between coroutines and threads depends on the specific scenario and requirements. With Kotlin coroutines gaining popularity in the Java ecosystem, many middleware and frameworks now support them, though traditional multithreaded distributed concurrency frameworks such as Akka or Spring WebFlux can achieve similar results.
Multithreading Knowledge Summary
Basics
Refer to the author's previous blog for detailed fundamentals.
Reactive Programming
Concurrent Time Retrieval Issue
The slowdown of System.currentTimeMillis originates from its underlying gettimeofday() system call, which requires a user‑to‑kernel mode switch and is affected by the Linux timer source (e.g., HPET). A single global clock source leads to contention under high concurrency, so middleware often uses a singleton thread for time acquisition.
Reference article: http://pzemtsov.github.io/2017/07/23/the-slow-currenttimemillis.html
public final class TimeUtil {
private static volatile long currentTimeMillis;
static {
currentTimeMillis = System.currentTimeMillis();
Thread daemon = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
currentTimeMillis = System.currentTimeMillis();
try {
TimeUnit.MILLISECONDS.sleep(1);
} catch (Throwable e) {
}
}
}
});
daemon.setDaemon(true);
daemon.setName("sentinel-time-tick-thread");
daemon.start();
}
public static long currentTimeMillis() {
return currentTimeMillis;
}
}JSON Serialization Performance Issue
List<VendorAllVo> vendorList = vendorInfoMapper.findVendorList();
log.info("日志输出:{}", JSONUtils.toJSONString(vendorList));
if (log.isInfoEnabled()){
log.info("日志输出:{}", JSONUtils.toJSONString(vendorList));
}Low‑Performance User Retrieval Example
class User {
private long id;
private String name;
private String email;
}
public User getUserInfoLowPerformance(long userId) {
String key = USER_INFO_KEY + ":" + userId;
String jsonUser = (String) redisTemplate.opsForValue().get(key);
if (jsonUser == null) {
return null;
}
return new Gson().fromJson(jsonUser, User.class);
}
public User getUserInfo(long userId) {
Map<String, Object> userInfoMap = redisTemplate.opsForHash().entries(key);
if (userInfoMap.isEmpty()) {
return null;
}
User user = new User();
user.setId((Long) userInfoMap.get("id"));
user.setName((String) userInfoMap.get("name"));
user.setEmail((String) userInfoMap.get("email"));
// ...
return user;
}Performance Optimization Strategies
Loop processing can amplify low‑performance code; consider asynchronous or non‑blocking operations where appropriate.
Use O(1) data lookup, leverage I/O, caching, disk, and CPU efficiently.
Adopt stream programming, Caffeine cache, batch processing optimizations to reduce blocking.
Lock selection (mutex, spin, read/write, optimistic, pessimistic, segment, CAS) significantly impacts concurrency.
UMP and Taishan Monitoring
UMP provides second‑level monitoring and integrates with Taishan for tracing, hardware monitoring, load balancing, etc. It allows manual activation of second‑level monitoring with a limit of 50 interfaces.
Metrics such as Tp99/Tp999, availability, and per‑machine traffic can be drilled down and adjusted via NP platform weight and load‑balancing strategies.
Concurrency & IO
Concurrency, Memory & CPU
JVM memory model differences across JDK versions, ensuring memory visibility, instruction reordering rules, and thread scheduling. JVM GC parameters, user‑mode to kernel‑mode switches.
Concurrency & Locks
Choosing different locks (mutex, spin, read/write, optimistic, pessimistic, segment, CAS) has a large impact on concurrency.
Concurrency & Middleware
Databases, caches, circuit breakers (rate limiting, degradation, merging), inter‑service calls, configuration centers, tracing, logging, JMQ. Benchmarks: TCP connection time, HTTP connection time; example performance figures for MySQL and Redis on various hardware.
Other Topics
Off‑heap memory : reduces GC, serialization/deserialization overhead.
Bytecode enhancement : can improve code performance (e.g., using GPT‑4 suggestions).
Cache lines : 64KB, Disruptor, false sharing (see https://tech.meituan.com/2016/11/18/disruptor.html ).
Locality principle & branch optimization : spatial locality via arrays, model structures; minimize branches, branch folding, condition merging.
Performance analysis tools such as flame graphs ( http://jagile.jd.com/shendeng/article/detail/1680 ) and techniques to write CPU‑friendly code.
Concurrency is also a strategy problem: selecting appropriate strategies can make data processing faster.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
JD Cloud Developers
JD Cloud Developers (Developer of JD Technology) is a JD Technology Group platform offering technical sharing and communication for AI, cloud computing, IoT and related developers. It publishes JD product technical information, industry content, and tech event news. Embrace technology and partner with developers to envision the future.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
