原子/易失性/同步在内部是如何工作的?

以下代码块之间的区别是什么?

代码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 }
}

我认为两个线程不能同时进入一个同步块...我说得对吗?如果这是真的,那么没有synchronizedatomic.incrementAndGet()怎么工作呢?它是线程安全的吗?

对易失性变量/原子变量的内部读取和写入有什么区别?我在一篇文章中读到,线程有一个变量的本地副本——那是什么?

推荐答案

你是在问他们是怎么做到的,所以给你:

No synchronization

private int counter;

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

它基本上是从内存中读取值,增加值,然后放回内存.它可以在单线程中工作,但如今,在多核、多CPU、多级缓存的时代,它无法正常工作.首先,它引入了竞争条件(多个线程可以同时读取值),但也存在可见性问题.该值可能只存储在"local"CPU内存(一些缓存)中,对于其他CPU/内核(以及线程)不可见.这就是为什么很多人会引用线程中变量的local copy.这是非常不安全的.考虑这个流行但中 break line 程停止代码:

private boolean stopped;

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

public void pleaseStop() {
    stopped = true;
}

volatile添加到stopped变量,它就可以正常工作了——如果任何其他线程通过pleaseStop()方法修改stopped变量,那么可以保证在工作线程的while(!stopped)循环中立即看到这种变化.顺便说一句,这也不是中 break line 程的好方法,请参见:How to stop a thread that is running forever without any useStopping a specific java thread.

AtomicInteger

private AtomicInteger counter = new AtomicInteger();

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

AtomicInteger类使用CAS(compare-and-swap)低级CPU操作(无需同步!)仅当当前值等于其他值(并且已成功返回)时,它们才允许您修改特定变量.因此,当执行getAndIncrement()时,它实际上在一个循环中运行(简化的实际实现):

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

因此基本上是:读取;try 存储递增的值;如果不成功(该值不再等于current),则读取并重试.compareAndSet()以本机代码(汇编)实现.

volatile without synchronization

private volatile int counter;

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

此代码不正确.它修复了可见性问题(volatile确保其他线程可以看到对counter所做的更改),但仍然存在争用条件.这已经是explained多次了:前/后增量不是原子的.

volatile的唯一副作用是"flushing"缓存,以便所有其他方都能看到最新版本的数据.这在大多数情况下过于严格;这就是为什么volatile不是默认值.

volatile without synchronization (2)

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

与上面的问题相同,但更糟糕的是,i不是private.比赛条件仍然存在.为什么会有问题?例如,如果两个线程同时运行此代码,则输出可能是+ 5+ 10.然而,你肯定会看到变化.

Multiple independent synchronized

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

令人惊讶的是,这个代码也不正确.事实上,这是完全错误的.首先,您正在i上同步,这将被更改(此外,i是一个原语,所以我猜您正在通过自动装箱创建的临时Integer上同步…)完全有缺陷.你也可以写:

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

没有两个线程可以进入同一个synchronized挡路with the same lock.在这种情况下(与您的代码类似),每次执行时锁对象都会更改,因此synchronized实际上没有任何效果.

即使使用了最后一个变量(或this)进行同步,代码仍然不正确.两个线程可以首先同步读取itemp(在temp中具有相同的本地值),然后第一个线程为i分配一个新值(例如,从1到6),另一个线程执行相同的操作(从1到6).

同步必须从读取到赋值.第一次同步没有效果(读取int是原子级的),第二次同步也没有效果.在我看来,以下是正确的形式:

void synchronized incIBy5() {
  i += 5 
}

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

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

Java相关问答推荐

在数组中使用意想不到的结果

[145007:JC_ILLEGAL_SYMBOL_CHARACTER保存时:griddb中的牧师

Spring bootstrap @ Asmat注释与@ Routed

基于仅存在于父级中的字段查询子文档?

具有额外列的Hibert多对多关系在添加关系时返回NonUniqueHealthExcellent

在for—each循环中的AnimationTimer中的if语句'

为什么我们不能实现两个接口,其中一个接口有相同的签名,其中一个接口有默认的实现在java?'

Java FX中的河内之塔游戏-在游戏完全解决之前什么都不会显示

条件加载@ManyToMany JPA

Java 21 struct 化连接货币,需要可预知的子任务异常排序

如何解释Java中for-each循环中对Iterable的强制转换方法引用?

无法在Java中处理PayPal支付响应

SpringBoot Kafka自动配置-适用于SASL_PLAYTEXT的SSLBundle 包,带SCRAM-SHA-512

每次FXMLLoader调用ApplationConext.getBean(类)时创建@Component的新实例

为什么当我创建Robot对象时,JavaFX引发IlLegalStateException异常?

如何在Cosmos DB(Java SDK)中增加默认响应大小

无法在IntStream上应用Collectors.groupingBy

整数->;双取消框,但双->;int不';t开箱.为什么?

错误:JOIN/ON的参数必须是boolean类型,而不是bigint类型.Java Spring启动应用程序

Java方法参数:括号中的类型声明?