英文:
Alternating Output Number and Letter in 2 Threads without Lock
问题
以下是您提供的代码的翻译部分:
public class App {
static volatile int x = 0;
static volatile boolean numPrinted = false;
static class NumThread implements Runnable {
@Override
public void run() {
while (x < 26) {
if (!numPrinted) {
System.out.print(x + 1);
numPrinted = true;
}
}
}
}
static class LetterThread implements Runnable {
@Override
public void run() {
while (x < 26) {
if (numPrinted) {
System.out.print((char) ('A' + x));
++x;
numPrinted = false;
}
}
}
}
public static void main(String[] args) {
Thread foo = new Thread(new NumThread());
Thread bar = new Thread(new LetterThread());
foo.start();
bar.start();
try {
foo.join();
bar.join();
} catch (InterruptedException ignored) {
}
System.out.println();
}
}
关于您的代码,大约每十次运行中会有一次出现如下输出:
1A2B3C4D5E6F7G8H9I10J11K12L13M14N15O16P17Q18R19S20T21U22V23W24X25Y26Z27
您的代码存在并发问题吗?我有意避免使用AtomicInteger
。
英文:
In an interview I was asked to use 2 and only 2 threads to print the string 1A2B3C...24X25Y26Z
, where one thread only prints numbers and the other only prints letter. I wrote the following code:
public class App {
static volatile int x = 0;
static volatile boolean numPrinted = false;
static class NumThread implements Runnable {
@Override
public void run() {
while (x < 26) {
if (!numPrinted) {
System.out.print(x + 1);
numPrinted = true;
}
}
}
}
static class LetterThread implements Runnable {
@Override
public void run() {
while (x < 26) {
if (numPrinted) {
System.out.print((char) ('A' + x));
++x;
numPrinted = false;
}
}
}
}
public static void main(String[] args) {
Thread foo = new Thread(new NumThread());
Thread bar = new Thread(new LetterThread());
foo.start();
bar.start();
try {
foo.join();
bar.join();
} catch (InterruptedException ignored) {
}
System.out.println();
}
}
In about 1 out of 10 runs, it will produce
1A2B3C4D5E6F7G8H9I10J11K12L13M14N15O16P17Q18R19S20T21U22V23W24X25Y26Z27
What's the concurrency issue with my code? I avoid AtomicInteger
intentionally.
答案1
得分: 1
两个线程都在忙等待。所以这是可能的:
- LetterThread 打印 Z
- NumThread 检查是否
x<26
,是的 - LetterThread 增加 x,设置 numPrinted=false
- NumThread 检查 numPrinted,它是 false,因此打印 27
在 NumThread 在 numPrinted 之后再次检查 x。
英文:
Both threads are busy-waiting. So this is possible:
- LetterThread prints Z
- NumThread checks if
x<26
, it is - LetterThread increments x, sets numPrinted=false
- NumThread check numPrinted, it is false, so prints 27
In NumThred check x again after numPrinted.
答案2
得分: 0
您的代码存在两个类成员x和numPrinted的竞态条件。
您可以尝试实现互斥锁和条件变量。
以下是一个每次都能正常工作的Ada示例:
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure Main is
protected traffic_Cop is
entry Nums (Val : Positive);
entry Alpha (Val : Character);
private
gate : Boolean := True;
end traffic_Cop;
protected body traffic_cop is
entry Nums (Val : Positive) when gate is
begin
Put (Item => Val, Width => 1);
gate := False;
end Nums;
entry Alpha (Val : Character) when not gate is
begin
Put (Val);
gate := True;
end Alpha;
end traffic_cop;
task printnums;
task printalpha;
task body printnums is
begin
for value in 1 .. 26 loop
traffic_cop.Nums (value);
end loop;
end printnums;
task body printalpha is
subtype chars is Character range 'A' .. 'Z';
begin
for value in chars loop
traffic_Cop.Alpha (value);
end loop;
end printalpha;
begin
null;
end Main;
该示例使用一个受保护对象和两个任务。Ada受保护对象可以保护免受不适当的竞态条件干扰。任务通常映射到操作系统线程。
受保护对象可以公开三种类型的方法:
-
受保护函数隐式地建立和实现共享读锁,允许多个任务同时从受保护对象中读取数据。
-
受保护过程隐式地建立和实现排他读写锁,只允许一个任务随时无条件地读取或修改受保护对象内的数据。
-
受保护入口隐式地建立和实现具有入口条件的排他读写锁。调用入口的任务只有在与入口关联的边界条件评估为True时才能读取和/或修改受保护对象的内容。
上面的示例使用了两个入口。
受保护对象traffic_cop仅包含一个变量,名为gate,它的初始值为True。
受保护体包含了两个入口的实现。入口Nums只能在gate为True时执行。入口Alpha只能在gate为false时执行。
声明了两个任务。
任务printnums循环遍历值1到26。在每次循环迭代中,任务调用traffic_cop.nums然后打印数值。
任务printalpha循环遍历值'A'到'Z'。在每次循环迭代中,任务调用traffic_cop.alpha然后打印字母值。
当到达主过程的"begin"语句时,这两个任务会自动启动。主过程什么都不做,这由"null;"命令明确表示。
程序的输出是:
1A2B3C4D5E6F7G8H9I10J11K12L13M14N15O16P17Q18R19S20T21U22V23W24X25Y26Z
英文:
Your code has a race condition on the two class members x and numPrinted.
You might try implementing a mutex and a condition variable.
The following is an Ada example that works correctly every time.
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
procedure Main is
protected traffic_Cop is
entry Nums (Val : Positive);
entry Alpha (Val : Character);
private
gate : Boolean := True;
end traffic_Cop;
protected body traffic_cop is
entry Nums (Val : Positive) when gate is
begin
Put (Item => Val, Width => 1);
gate := False;
end Nums;
entry Alpha (Val : Character) when not gate is
begin
Put (Val);
gate := True;
end Alpha;
end traffic_cop;
task printnums;
task printalpha;
task body printnums is
begin
for value in 1 .. 26 loop
traffic_cop.Nums (value);
end loop;
end printnums;
task body printalpha is
subtype chars is Character range 'A' .. 'Z';
begin
for value in chars loop
traffic_Cop.Alpha (value);
end loop;
end printalpha;
begin
null;
end Main;
The example uses one protected object and two tasks. An Ada protected object is protected from inappropriate race conditions. A task is often mapped to an operating system thread.
Protected objects can expose three kinds of methods.
A protected function implicitly establishes and implements a shared read lock, allowing multiple tasks to simultaneously read data from the protected object.
A protected procedure implicitly establishes and implements an exclusive read-write lock, allowing only one task at a time to unconditionally read or modify data within the protected object.
A protected entry implicitly establishes and implements an exclusive read-write lock along with an entry condition. A task calling an entry can read and/or modify the contents of the protected object only when the boundary condition associated with the entry evaluates to True.
The example above uses two entries.
The protected object traffic_cop contains only one variable, a Boolean value named gate which is initialized to the value True.
The protected body contains the implementation of the two entries. The entry Nums can only execute when gate is True. The entry Alpha can only execute then gate is false.
Two tasks are declared.
The task printnums loops through the values 1 through 26. In each loop iteration the task calls traffic_cop.nums then prints the numeric value.
The task printalpha loops through the values 'A' through 'Z'. In each loop iteration the task calls traffic_cop.alpha then prints the alpha value.
Both tasks automatically start when the "begin" statement for the main procedure is reached. The main procedure does nothing, which is explicitly indicated by the "null;" command.
The output of the program is:
1A2B3C4D5E6F7G8H9I10J11K12L13M14N15O16P17Q18R19S20T21U22V23W24X25Y26Z
答案3
得分: 0
这个问题可以通过使用只有一个共享变量来更容易地解决,这种情况下是 numPrinted
。这个变量表示哪个线程需要执行某些操作。线程本身可以跟踪它们的进度,等待直到它们需要打印内容,然后打印内容并且翻转对两个线程都可见的 numPrinted
变量。代码示例如下:
static volatile boolean numPrinted = false;
static class NumThread implements Runnable {
@Override
public void run() {
for (int number = 1; number <= 26; number++) {
// 等待直到需要打印数字
while (numPrinted) {
Thread.yield(); // 可选
};
// 打印数字
System.out.print(number);
// 设置标志表示我们打印了数字
numPrinted = true;
}
}
}
static class LetterThread implements Runnable {
@Override
public void run() {
for (char letter = 'A'; letter <= 'Z'; letter++) {
// 等待直到需要打印字母
while (!numPrinted) {
Thread.yield(); // 可选
}
// 打印字母
System.out.print(letter);
// 设置标志表示我们打印了字母
numPrinted = false;
}
}
}
英文:
This problem is easier solved by using just one shared variable, in this case numPrinted
. This indicates which thread needs to do something. The threads themselves can keep track of their progress, wait until they need to print something, print it and then flip the numPrinted
variable that is visible to both threads. This could look like:
static volatile boolean numPrinted = false;
static class NumThread implements Runnable {
@Override
public void run() {
for (int number = 1; number <= 26; number++) {
// Wait until we need to print a number
while (numPrinted) {
Thread.yield(); //optional
};
// Print a number
System.out.print(number);
// Set flag that we printed a number
numPrinted = true;
}
}
}
static class LetterThread implements Runnable {
@Override
public void run() {
for (char letter = 'A'; letter <= 'Z'; letter++) {
// Wait until we need to print a letter
while (!numPrinted) {
Thread.yield(); //optional
}
// Print a letter
System.out.print(letter);
//set flag that we printed a letter
numPrinted = false;
}
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论