Аккаунт Войти / Регистрация
Тема
Размер текста
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, вы принимаете условия политики конфиденциальности

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

Java under the Microscope: Stack, Heap, and GC using Code Examples
Diagram - Java Memory Model - Heap / Non-Heap / Stack Heap (memory for objects) Creates objects via new. Young Generation: Eden + Survivor. Old Generation: objects that have survived several GC c…
Welcome to my IT blog, my name is Vitaly Lesnykh. I ve been in IT since 2004: I created my first website as a thesis in college, and I ve been developing apps and websites since then. I ve worked as …
Breaking down: Rate-limiter, non-blocking operations, scheduler Go vs Java | Concurrency part 4
This article is dedicated to understanding the principles of working with concurrency and synchronization in Go and Java. We will look at key approaches such as rate-limiter, non-blocking operations, …

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

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. …