Аккаунт Войти / Регистрация
Тема
Размер текста
Go vs. Java - Comparing Memory Models - Part 2: Atomic Operations, Preemption, Defer/Finally, Context, Escape Analysis, GC, False Sharing

Go vs. Java - Comparing Memory Models - Part 2: Atomic Operations, Preemption, Defer/Finally, Context, Escape Analysis, GC, False Sharing

Atomic operations

Atomic operations ensure correct execution of variable operations without race conditions, guaranteeing a happens-before between reads and writes.

Go example:

import "sync/atomic"

var counter int32

func increment() {
atomic.AddInt32(&counter, 1) // atomic increment
}

Java example:

import java.util.concurrent.atomic.AtomicInteger;

AtomicInteger counter = new AtomicInteger();

void increment() {
counter.incrementAndGet(); // atomic increment
}

Preemption / Scheduler Effects

Preemption is when the scheduler interrupts the execution of a thread/goroutine. This is important for happens-before, since the order of execution is not guaranteed without synchronization.

Go example:

go func() {
println("Task 1")
}()
go func() {
println("Task 2")
}()
runtime.Gosched() // gives other goroutines a chance to execute

Java example:

Thread t1 = new Thread(() -> System.out.println("Task 1"));
Thread t2 = new Thread(() -> System.out.println("Task 2"));
t1.start();
t2.start();
Thread.yield(); // gives other threads a chance

Defer / Finally as synchronous endpoints

Defer in Go and finally in Java are executed after the function/block exits, making them convenient for releasing resources and synchronizing.

Go example:

mu.Lock()
defer mu.Unlock() // guarantees unlocking even during a panic

Java example:

lock.lock();
try {
// code
} finally {
lock.unlock(); // guaranteed release
}

Context propagation / cancellation (Go)

Context allows you to manage the lifecycle of goroutines and the propagation of cancellation events, which is directly related to happens-before.

Go example:

ctx, cancel := context.WithCancel(context.Background())

go func(ctx context.Context) {
select {
case <-ctx.Done():
fmt.Println("Cancelled")
}
}(ctx)

cancel() // cancels execution of a goroutine

Escape analysis / locals -> heap

Escape analysis determines whether a variable goes to the heap or remains on the stack. Indirectly affects visibility, since objects on the heap are accessible to other goroutines/threads.

Go example:

func getPointer() *int {
x := 42
return &x // x "escapes" into the heap
}

Java example:

class Box { int value; }

Box makeBox() {
Box b = new Box();
return b; // object on the heap, accessible to other threads
}

GC pause / stop-the-world

Pausing the garbage collector can temporarily stop all threads/goroutines. In Go and Java, this is important for understanding ordering and happens-before.

Go example:

runtime.GC() // Forced GC
//→ Initiates GC, trying to minimize pauses, but a complete stop-world pause is possible.

Java example:

System.gc(); // Forced GC
//→ JVM recommendation to perform GC, not guaranteed.

False sharing / cache effects

False sharing occurs when multiple threads modify different variables located in the same cache line. This can impair visibility and performance.

Go example:

type PaddedCounter struct {
value int64
_ [7]int64 // padding to prevent false sharing
}
Go: _[7]int64 — padding prevents false sharing on 64-bit systems, but the cache line size can be larger (usually 64 bytes). Sometimes, more precise packages/structures are used for alignment.

Java example:

class PaddedCounter {
volatile long value;
long p1,p2,p3,p4,p5,p6,p7; // padding
}
Java: volatile isn't really needed for padding; its main purpose is visibility between threads. The key here is padding, so that the value appears as a separate cache line. On modern JVMs, you can use @Contended annotations (with the -XX:-RestrictContended JVM option enabled) to have the JVM add padding automatically.
Concept Go example Java example What influences
Atomic operations atomic.AddInt32(&counter, 1) counter.incrementAndGet() Guarantees atomicity, happens-before between reads and writes
Preemption / Scheduler runtime.Gosched() Thread.yield() Affects the execution order of goroutines/threads and the visibility of changes
Defer / Finally defer mu.Unlock() finally { lock.unlock(); } Resource release, synchronous points after function/block exit
Context propagation / cancellation context.WithCancel() Happens-before propagation via goroutine cancellation
Escape analysis / local → heap return &x return new Box() Determines where the object is stored (stack/heap), indirectly affects visibility
GC pause / stop-the-world runtime.GC() System.gc() Temporarily stops threads, affects ordering and synchronization
False sharing / cache effects type PaddedCounter struct { value int64; _ [7]int64 } class PaddedCounter { volatile long value; long p1,p2,p3,p4,p5,p6,p7; } May disrupt visibility and reduce performance

Summary

In this article, we covered advanced aspects of the memory model and multithreading in Go and Java. Atomic operations, preemption, defer/finally, context propagation, escape analysis, GC pause, and false sharing—all of these mechanisms directly or indirectly affect happens-before, change visibility, and code execution order.

Go and Java have similar concepts, but they are implemented differently: Go often uses lightweight goroutines and context for lifecycle management, while Java uses heavyweight threads and volatile/atomic classes. Understanding these nuances is critical for writing correct, safe, and high-performance code in a multithreaded environment.

The final table demonstrates how specific constructs and mechanisms affect the memory model and synchronization between threads/goroutines.

Оставить комментарий

Мой канал в социальных сетях
Отправляя email, вы принимаете условия политики конфиденциальности

Полезные статьи:

Go vs Java - comparison of memory models: happens-before, visibility, reorder, synchronization events, write/read barriers
Memory model is a layer between the program and the processor. Modern CPUs aggressively optimize execution: instructions may be reordered, data may be stored in core caches, and operations may be perf…
Java v25: Choosing the Right Multithreading for Any Task
Introduction The Java world is rapidly evolving, and with each version, new tools are emerging for effectively working with multithreading, collections, and asynchrony. Java 25 brings powerful feature…
Understanding multithreading in Java through collections and atomics
Understanding Multithreading in Java through Collections and Atomics 1️⃣ HashMap / TreeMap / TreeSet (not thread-safe) HashMap: Structure: array of buckets + linked lists / trees (for collisions). U…

Новые статьи:

Concurrency is not about “starting many threads”. It’s about agreements between them. Imagine a restaurant kitchen: — cooks (threads / goroutines) — orders (tasks) — and the main question: how do th…
When HashMap starts killing production: the engineering story of ConcurrentHashMap
Imagine a typical production service. 32 CPU hundreds of threads configuration / session / rate limits cache tens of thousands of operations per second And somewhere inside — a regular Map. At first…
Zero Allocation in Java: what it is and why it matters
Zero Allocation — is an approach to writing code in which no unnecessary objects are created in heap memory during runtime. The main idea: fewer objects → less GC → higher stability and performance. …