Вопрос-ответ

What is the difference between atomic / volatile / synchronized?

В чем разница между atomic / volatile / synchronized?

Как atomic / volatile / synchronized работают внутри?

В чем разница между следующими блоками кода?

Код 1

private int counter;

public int getNextUniqueIndex() {
return counter++;
}

Код 2

private AtomicInteger counter;

public int getNextUniqueIndex() {
return counter.getAndIncrement();
}

Код 3

private volatile int counter;

public int getNextUniqueIndex() {
return counter++;
}

volatile Работает следующим образом? Является

volatile int i = 0;
void incIBy5() {
i += 5;
}

эквивалентно

Integer i = 5;
void incIBy5() {
int temp;
synchronized(i) { temp = i }
synchronized(i) { i = temp + 5 }
}

Я думаю, что два потока не могут одновременно входить в синхронизированный блок ... я прав? Если это правда, то как atomic.incrementAndGet() работает без synchronized? И является ли это потокобезопасным?

И в чем разница между внутренним чтением и записью в volatile переменные / атомарные переменные? Я читал в какой-то статье, что поток имеет локальную копию переменных - что это?

Переведено автоматически
Ответ 1

Вы конкретно спрашиваете о том, как они работают внутри, так что вот вы где:

Нет синхронизации

private int counter;

public int getNextUniqueIndex() {
return counter++;
}

В основном он считывает значение из памяти, увеличивает его и помещает обратно в память. Это работает в одном потоке, но в настоящее время, в эпоху многоядерных, многопроцессорных и многоуровневых кэшей, это не будет работать корректно. Прежде всего, это приводит к возникновению условия гонки (несколько потоков могут считывать значение одновременно), но также к проблемам с видимостью. Значение может храниться только в "локальной" памяти процессора (некотором кэше) и не быть видимым для других процессоров / ядер (и, следовательно, потоков). Вот почему многие ссылаются на локальную копию переменной в потоке. Это очень небезопасно. Рассмотрим этот популярный, но неработающий код, останавливающий поток:

private boolean stopped;

public void run() {
while(!stopped) {
//do some work
}
}

public void pleaseStop() {
stopped = true;
}

Добавьте volatile в stopped переменную, и она будет работать нормально - если какой-либо другой поток изменит stopped переменную с помощью pleaseStop() метода, вы гарантированно увидите это изменение немедленно в while(!stopped) цикле рабочего потока. Кстати, это тоже не лучший способ прервать поток, смотрите: Как остановить поток, который выполняется вечно без какого-либо использования и остановка определенного потока java.

AtomicInteger

private AtomicInteger counter = new AtomicInteger();

public int getNextUniqueIndex() {
return counter.getAndIncrement();
}

The AtomicInteger class uses CAS (compare-and-swap) low-level CPU operations (no synchronization needed!) They allow you to modify a particular variable only if the present value is equal to something else (and is returned successfully). So when you execute getAndIncrement() it actually runs in a loop (simplified real implementation):

int current;
do {
current = get();
} while(!compareAndSet(current, current + 1));

So basically: read; try to store incremented value; if not successful (the value is no longer equal to current), read and try again. The compareAndSet() is implemented in native code (assembly).

volatile without synchronization

private volatile int counter;

public int getNextUniqueIndex() {
return counter++;
}

This code is not correct. It fixes the visibility issue (volatile makes sure other threads can see change made to counter) but still has a race condition. This has been explained multiple times: pre/post-incrementation is not atomic.

The only side effect of volatile is "flushing" caches so that all other parties see the freshest version of the data. This is too strict in most situations; that is why volatile is not default.

volatile without synchronization (2)

volatile int i = 0;
void incIBy5() {
i += 5;
}

The same problem as above, but even worse because i is not private. The race condition is still present. Why is it a problem? If, say, two threads run this code simultaneously, the output might be + 5 or + 10. However, you are guaranteed to see the change.

Multiple independent synchronized

void incIBy5() {
int temp;
synchronized(i) { temp = i }
synchronized(i) { i = temp + 5 }
}

Surprise, this code is incorrect as well. In fact, it is completely wrong. First of all you are synchronizing on i, which is about to be changed (moreover, i is a primitive, so I guess you are synchronizing on a temporary Integer created via autoboxing...) Completely flawed. You could also write:

synchronized(new Object()) {
//thread-safe, SRSLy?
}

No two threads can enter the same synchronized block with the same lock. In this case (and similarly in your code) the lock object changes upon every execution, so synchronized effectively has no effect.

Even if you have used a final variable (or this) for synchronization, the code is still incorrect. Two threads can first read i to temp synchronously (having the same value locally in temp), then the first assigns a new value to i (say, from 1 to 6) and the other one does the same thing (from 1 to 6).

The synchronization must span from reading to assigning a value. Your first synchronization has no effect (reading an int is atomic) and the second as well. In my opinion, these are the correct forms:

void synchronized incIBy5() {
i += 5
}

void incIBy5() {
synchronized(this) {
i += 5
}
}

void incIBy5() {
synchronized(this) {
int temp = i;
i = temp + 5;
}
}
Ответ 2

Declaring a variable as volatile means that modifying its value immediately affects the actual memory storage for the variable. The compiler cannot optimize away any references made to the variable. This guarantees that when one thread modifies the variable, all other threads see the new value immediately. (This is not guaranteed for non-volatile variables.)

Declaring an atomic variable guarantees that operations made on the variable occur in an atomic fashion, i.e., that all of the substeps of the operation are completed within the thread they are executed and are not interrupted by other threads. For example, an increment-and-test operation requires the variable to be incremented and then compared to another value; an atomic operation guarantees that both of these steps will be completed as if they were a single indivisible/uninterruptible operation.

Synchronizing all accesses to a variable allows only a single thread at a time to access the variable, and forces all other threads to wait for that accessing thread to release its access to the variable.

Synchronized access is similar to atomic access, but the atomic operations are generally implemented at a lower level of programming. Also, it is entirely possible to synchronize only some accesses to a variable and allow other accesses to be unsynchronized (e.g., synchronize all writes to a variable but none of the reads from it).

Atomicity, synchronization, and volatility are independent attributes, but are typically used in combination to enforce proper thread cooperation for accessing variables.

Addendum (April 2016)

Synchronized access to a variable is usually implemented using a monitor or semaphore. These are low-level mutex (mutual exclusion) mechanisms that allow a thread to acquire control of a variable or block of code exclusively, forcing all other threads to wait if they also attempt to acquire the same mutex. Once the owning thread releases the mutex, another thread can acquire the mutex in turn.

Addendum (July 2016)

Synchronization occurs on an object. This means that calling a synchronized method of a class will lock the this object of the call. Static synchronized methods will lock the Class object itself.

Likewise, entering a synchronized block requires locking the this object of the method.

This means that a synchronized method (or block) can be executing in multiple threads at the same time if they are locking on different objects, but only one thread can execute a synchronized method (or block) at a time for any given single object.

Ответ 3

volatile:

volatile is a keyword. volatile forces all threads to get latest value of the variable from main memory instead of cache. All threads can access volatile variable value at same time with out lock.

It reduces memory consistency error.

When to use: One thread modifies the data and other threads have to read latest value of data. Other threads will take some action with out updating
data
.

AtomicXXX:


AtomicXXX classes support lock-free thread-safe programming on single variables.


These AtomicXXX classes (like AtomicInteger) resolves memory inconsistency errors

When to use: Multiple threads can read and modify data.

synchronized:

synchronized is keyword used to guard a method or code block. By making method as synchronized , you achieve two things.



  1. Two executions of synchronized methods on the same object never run

  2. Change in the object state is visible to other threads


When to use: Multiple threads can read and modify data. Your business logic not only update the data but also executes atomic operations

AtomicXXX is equivalent of volatile + synchronized even though the implementation is different.

AmtomicXXX extends volatile variables + compareAndSet methods but does not use synchronization.

Ответ 4

I know that two threads can not enter in Synchronize block at the same time


Two thread cannot enter a synchronized block on the same object twice. This means that two threads can enter the same block on different objects. This confusion can lead to code like this.

private Integer i = 0;

synchronized(i) {
i++;
}

This will not behave as expected as it could be locking on a different object each time.


if this is true than How this atomic.incrementAndGet() works without Synchronize ?? and is thread safe ??


yes. It doesn't use locking to achieve thread safety.

If you want to know how they work in more detail, you can read the code for them.


And what is difference between internal reading and writing to Volatile Variable / Atomic Variable ??


Atomic class uses volatile fields. There is no difference in the field. The difference is the operations performed. The Atomic classes use CompareAndSwap or CAS operations.


i read in some article that thread has local copy of variables what is that ??


I can only assume that it referring to the fact that each CPU has its own cached view of memory which can be different from every other CPU. To ensure that your CPU has a consistent view of data, you need to use thread safety techniques.

This is only an issue when memory is shared at least one thread updates it.

2023-09-25 08:31 java multithreading