在没有锁的情况下,在两个线程中交替输出数字和字母。

huangapple go评论79阅读模式
英文:

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 &lt; 26) {
                if (!numPrinted) {
                    System.out.print(x + 1);
                    numPrinted = true;
                }
            }
        }
    }

    static class LetterThread implements Runnable {
        @Override
        public void run() {
            while (x &lt; 26) {
                if (numPrinted) {
                    System.out.print((char) (&#39;A&#39; + 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&lt;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 =&gt; Val, Width =&gt; 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 &#39;A&#39; .. &#39;Z&#39;;
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 &lt;= 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 = &#39;A&#39;; letter &lt;= &#39;Z&#39;; 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;
}
}
}

huangapple
  • 本文由 发表于 2020年10月6日 12:31:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/64219381.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定