需要了解多线程环境下AtomicInteger代码使用中的问题
Need to understand the problem in AtomicInteger code usage in multithreaded environment
在一次面试中,有人问我一个编码问题,我必须找到该代码中的问题并提出适当的解决方案。
完整代码请见下方:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
public class Atomic {
static AtomicInteger count = new AtomicInteger(0);
static int counter = 0;
public static class Runnable extends Thread {
public void run() {
while (count.getAndSet(1) != 0) {
try {
Thread.sleep(3000);
} catch (Exception e) {
}
}
counter = counter + 1;
count.set(0);
}
}
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
Runnable runnable = new Runnable();
executor.execute(runnable);
}
executor.shutdown();
}
}
此代码 运行 正确。但问题是,如果线程数增加或者我 运行 For 循环将近 10000 次,这段代码就会出现问题。
我试图找出问题所在,但找不到。
这段代码有几处错误。你还没有用 "there is some problem" 表示,但这里有一些东西跳出来了。
首先,counter
变量未安全更新。多个线程不能保证最后写入的值的可见性;您也不能保证在读取和写入之间没有其他线程更新其值。
对此的简单解决方案:将 counter
更改为 AtomicInteger
,然后使用 getAndIncrement
或 incrementAndGet
递增。
其次,public static class Runnable extends Thread {
非常可疑。
- 不隐藏大家熟知的名字Javaclasses(这是隐藏
java.lang.Runnable
)
- 不要直接扩展
Thread
,尤其是当您只需要 java.lang.Runnable
来添加带有 ExecutorService
. 的执行时
更合适的 class 声明是:
public static class MyRunnable implements Runnable {
(或者随便你怎么称呼它)
或者你可以只声明一个匿名 class:
executor.execute(new Runnable() { /* body */ });
或者您可以只声明一个 lambda:
executor.execute(() -> { /* body */ });
第三,count
在这里似乎并没有真正起到明显的作用。 runnable的逻辑好像是:
- 如果 "flag" 为假:
- 将 "flag" 设置为真
- 增加一个变量
- 将 "flag" 设置为 false
- 否则:
- 等待 3 秒
- 再试一次
count
在这里扮演着"flag"的角色。它实际上只是一个 AtomicBoolean
.
但是你根本不需要单独的 count
变量,如果你将 counter
设为 AtomicInteger
:
while (true) {
int current = counter.get();
if (counter.compareAndSet(current, current + 1)) {
// Nothing else is trying to update "current" at the same time:
// we updated it. Stop.
break;
}
// Something else is trying to update at the same time.
// Sleep for 3 seconds.
Thread.sleep(3000);
}
在一次面试中,有人问我一个编码问题,我必须找到该代码中的问题并提出适当的解决方案。
完整代码请见下方:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
public class Atomic {
static AtomicInteger count = new AtomicInteger(0);
static int counter = 0;
public static class Runnable extends Thread {
public void run() {
while (count.getAndSet(1) != 0) {
try {
Thread.sleep(3000);
} catch (Exception e) {
}
}
counter = counter + 1;
count.set(0);
}
}
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
Runnable runnable = new Runnable();
executor.execute(runnable);
}
executor.shutdown();
}
}
此代码 运行 正确。但问题是,如果线程数增加或者我 运行 For 循环将近 10000 次,这段代码就会出现问题。
我试图找出问题所在,但找不到。
这段代码有几处错误。你还没有用 "there is some problem" 表示,但这里有一些东西跳出来了。
首先,counter
变量未安全更新。多个线程不能保证最后写入的值的可见性;您也不能保证在读取和写入之间没有其他线程更新其值。
对此的简单解决方案:将 counter
更改为 AtomicInteger
,然后使用 getAndIncrement
或 incrementAndGet
递增。
其次,public static class Runnable extends Thread {
非常可疑。
- 不隐藏大家熟知的名字Javaclasses(这是隐藏
java.lang.Runnable
) - 不要直接扩展
Thread
,尤其是当您只需要java.lang.Runnable
来添加带有ExecutorService
. 的执行时
更合适的 class 声明是:
public static class MyRunnable implements Runnable {
(或者随便你怎么称呼它)
或者你可以只声明一个匿名 class:
executor.execute(new Runnable() { /* body */ });
或者您可以只声明一个 lambda:
executor.execute(() -> { /* body */ });
第三,count
在这里似乎并没有真正起到明显的作用。 runnable的逻辑好像是:
- 如果 "flag" 为假:
- 将 "flag" 设置为真
- 增加一个变量
- 将 "flag" 设置为 false
- 否则:
- 等待 3 秒
- 再试一次
count
在这里扮演着"flag"的角色。它实际上只是一个 AtomicBoolean
.
但是你根本不需要单独的 count
变量,如果你将 counter
设为 AtomicInteger
:
while (true) {
int current = counter.get();
if (counter.compareAndSet(current, current + 1)) {
// Nothing else is trying to update "current" at the same time:
// we updated it. Stop.
break;
}
// Something else is trying to update at the same time.
// Sleep for 3 seconds.
Thread.sleep(3000);
}