在 Java 中使用信号量
Using Semaphores in Java
我对在学校向 Whosebug 寻求帮助感到内疚,但我已经用尽了我的资源,无法在我的生活中解决这个问题。对于我的 类 之一,我需要了解如何在 Java 中构建和正确使用信号量。其中一个练习有以下代码:
import java.lang.Thread;
import java.util.concurrent.*;
public class ThreadSync
{
private static boolean runFlag = true;
public static void main( String[] args ) {
Runnable[] tasks = new Runnable[37];
Thread[] threads = new Thread[37];
// create 10 digit threads
for (int d=0; d<10; d++) {
tasks[d] = new PrintDigit(d);
threads[d] = new Thread( tasks[d] );
threads[d].start();
}
// create 26 letter threads
for (int d=0; d<26; d++) {
tasks[d+10] = new PrintLetter((char)('A'+d));
threads[d+10] = new Thread( tasks[d+10] );
threads[d+10].start();
}
// create a coordinator thread
tasks[36] = new PrintSlashes();
threads[36] = new Thread( tasks[36] );
threads[36].start();
// Let the threads to run for a period of time
try {
Thread.sleep(50);
}
catch (InterruptedException ex) {
ex.printStackTrace();
}
runFlag = false;
// Interrupt the threads
for (int i=0; i<37; i++) threads[i].interrupt();
}
public static class PrintDigit implements Runnable
{
int digit;
public PrintDigit(int d) { digit=d; }
public void run(){
while (runFlag) {
System.out.printf( "%d\n", digit);
}
}
}
public static class PrintLetter implements Runnable
{
char letter;
public PrintLetter(char c) { letter=c; }
public void run(){
while (runFlag) {
System.out.printf( "%c\n", letter);
}
}
}
public static class PrintSlashes implements Runnable
{
public void run(){
while (runFlag) {
System.out.printf( "%c\n", '/');
System.out.printf( "%c\n", '\');
}
}
}
}
我需要修改代码,只需要添加 "semaphore-related statements" 使程序重复打印出一个“/”后跟三个数字,然后是一个“\”后跟两个字母。
他们举的例子如下:
/156\BA/376\YZ/654\JK/257\HG/445\DD…
如有任何帮助,我们将不胜感激。我通常很擅长自己学习,但这些线程让我头晕目眩!谢谢!
我质疑这位讲师的教学方法和编码实践,但我会回答这个问题。事实上,我认为这个问题过于复杂,这让我更愿意回答它,而不是让你自己想办法。
该示例有点违反直觉,因为线程并未用于其正常目的,即允许并发执行,而只是作为理解信号量的练习。因此,信号量也必须以某种非标准的方式使用,作为线程之间的信号,而不是用于管理资源的正常使用。您将了解信号量在这种人为设计的情况下如何工作,但可能最终无法理解它们在正常情况下的使用方式。但是,可以通过阅读信号量 class 上的 Javadoc 来解决这个问题,所以回到您的讲师设计的案例。
很明显,线程 运行ning PrintSlashes.run() 旨在充当管理器,确定数字线程何时为 运行 以及字符线程何时为 运行.它需要告诉数字线什么时候可以运行,它需要告诉字符线什么时候可以运行。此外,它需要知道什么时候打印了三个数字,什么时候打印了两个字符。这就是需要传输的四条信息,最简单的模型就是使用四个信号量对象。
Semaphore 对象应该代表以下四种东西:
- 可打印的位数
- 已打印的数字(在最近的正斜杠之后)
- 可打印的字符数
- 已打印的字符数(在最近的反斜杠之后)
PrintDigit.run() 在打印每个数字之前应该从可用信号量的数字中获得许可;这允许数字可用信号量将数字打印一次限制为三个。打印数字后,该方法应从数字打印信号量(注意,不是可用数字信号量)释放许可,以指示已打印数字。我相信您可以弄清楚 PrintLetter.run() 应该做什么。顺便说一句,线程正在获取一个信号量但释放另一个信号量的事实是设计该示例的方式之一;通常线程会释放它们获取的相同信号量。
PrintSlashes.run() 应该在打印一个斜线后从可用信号量中释放三个许可,然后在打印一个反斜线之前从已打印信号量中获得三个许可。释放可用的三个数字允许 PrintDigit 线程打印三个数字,等待获取打印的三个数字确保在继续之前打印三个数字。同样,您应该能够弄清楚打印反斜杠后会发生什么。
请注意,数字信号量对象应使用 0 个许可进行初始化,以便数字线程将等待斜线线程启动。
另外两个注意事项:
要使代码如示例输出所示那样工作,您还需要从每个打印的字符串中删除 \n
,否则每个字符将位于不同的行上。但是,讲师可能希望每个字符在不同的行上,并且给了你错误的示例输出。你得猜猜他真正想要的是什么。
如果您想让您的代码防弹,您可能需要在 System.out 上进行同步,如以下内容所述:
Synchronization and System.out.println
但是,您的讲师可能不关心这个练习的问题。
最后,应进行以下更正以修复代码中的其他不良做法:
不应使用通配符导入,因为您没有使用并发包中的大量 classes。在我看来,永远不应该使用通配符导入。通配符导入会导致难以查看 classes 的来源,从而损害代码的易读性,而易读性是代码最重要的方面。
有意义的常量,比如这段代码中的“10”、“26”、“36”、“37”,不要写成字面量,而是应该使用定义好的常量,例如static final int NUMBER_OF_DIGITS = 10;
。然后代码本身可以使用符号 NUMBER_OF_DIGITS
,使其更易读且更易于维护,因为您可以轻松更改常量的值 - 例如,如果要将代码转换为八进制,则更改为 8 - 无需担心您会错过一些常数。
有逻辑关系的常量尤其不要写成字面量。在这种情况下,即使 static final int NUMBER_OF_CHARACTERS = 36
也不是好的做法;应该是static final int NUMBER_OF_CHARACTERS = NUMBER_OF_DIGITS + NUMBER_OF_LETTERS;
,逻辑和数值关系清楚
您是否真的想在您上交的作业中进行这些更正取决于您认为教师对给予良好更正的反应。
我对在学校向 Whosebug 寻求帮助感到内疚,但我已经用尽了我的资源,无法在我的生活中解决这个问题。对于我的 类 之一,我需要了解如何在 Java 中构建和正确使用信号量。其中一个练习有以下代码:
import java.lang.Thread;
import java.util.concurrent.*;
public class ThreadSync
{
private static boolean runFlag = true;
public static void main( String[] args ) {
Runnable[] tasks = new Runnable[37];
Thread[] threads = new Thread[37];
// create 10 digit threads
for (int d=0; d<10; d++) {
tasks[d] = new PrintDigit(d);
threads[d] = new Thread( tasks[d] );
threads[d].start();
}
// create 26 letter threads
for (int d=0; d<26; d++) {
tasks[d+10] = new PrintLetter((char)('A'+d));
threads[d+10] = new Thread( tasks[d+10] );
threads[d+10].start();
}
// create a coordinator thread
tasks[36] = new PrintSlashes();
threads[36] = new Thread( tasks[36] );
threads[36].start();
// Let the threads to run for a period of time
try {
Thread.sleep(50);
}
catch (InterruptedException ex) {
ex.printStackTrace();
}
runFlag = false;
// Interrupt the threads
for (int i=0; i<37; i++) threads[i].interrupt();
}
public static class PrintDigit implements Runnable
{
int digit;
public PrintDigit(int d) { digit=d; }
public void run(){
while (runFlag) {
System.out.printf( "%d\n", digit);
}
}
}
public static class PrintLetter implements Runnable
{
char letter;
public PrintLetter(char c) { letter=c; }
public void run(){
while (runFlag) {
System.out.printf( "%c\n", letter);
}
}
}
public static class PrintSlashes implements Runnable
{
public void run(){
while (runFlag) {
System.out.printf( "%c\n", '/');
System.out.printf( "%c\n", '\');
}
}
}
}
我需要修改代码,只需要添加 "semaphore-related statements" 使程序重复打印出一个“/”后跟三个数字,然后是一个“\”后跟两个字母。
他们举的例子如下:
/156\BA/376\YZ/654\JK/257\HG/445\DD…
如有任何帮助,我们将不胜感激。我通常很擅长自己学习,但这些线程让我头晕目眩!谢谢!
我质疑这位讲师的教学方法和编码实践,但我会回答这个问题。事实上,我认为这个问题过于复杂,这让我更愿意回答它,而不是让你自己想办法。
该示例有点违反直觉,因为线程并未用于其正常目的,即允许并发执行,而只是作为理解信号量的练习。因此,信号量也必须以某种非标准的方式使用,作为线程之间的信号,而不是用于管理资源的正常使用。您将了解信号量在这种人为设计的情况下如何工作,但可能最终无法理解它们在正常情况下的使用方式。但是,可以通过阅读信号量 class 上的 Javadoc 来解决这个问题,所以回到您的讲师设计的案例。
很明显,线程 运行ning PrintSlashes.run() 旨在充当管理器,确定数字线程何时为 运行 以及字符线程何时为 运行.它需要告诉数字线什么时候可以运行,它需要告诉字符线什么时候可以运行。此外,它需要知道什么时候打印了三个数字,什么时候打印了两个字符。这就是需要传输的四条信息,最简单的模型就是使用四个信号量对象。
Semaphore 对象应该代表以下四种东西:
- 可打印的位数
- 已打印的数字(在最近的正斜杠之后)
- 可打印的字符数
- 已打印的字符数(在最近的反斜杠之后)
PrintDigit.run() 在打印每个数字之前应该从可用信号量的数字中获得许可;这允许数字可用信号量将数字打印一次限制为三个。打印数字后,该方法应从数字打印信号量(注意,不是可用数字信号量)释放许可,以指示已打印数字。我相信您可以弄清楚 PrintLetter.run() 应该做什么。顺便说一句,线程正在获取一个信号量但释放另一个信号量的事实是设计该示例的方式之一;通常线程会释放它们获取的相同信号量。
PrintSlashes.run() 应该在打印一个斜线后从可用信号量中释放三个许可,然后在打印一个反斜线之前从已打印信号量中获得三个许可。释放可用的三个数字允许 PrintDigit 线程打印三个数字,等待获取打印的三个数字确保在继续之前打印三个数字。同样,您应该能够弄清楚打印反斜杠后会发生什么。
请注意,数字信号量对象应使用 0 个许可进行初始化,以便数字线程将等待斜线线程启动。
另外两个注意事项:
要使代码如示例输出所示那样工作,您还需要从每个打印的字符串中删除
\n
,否则每个字符将位于不同的行上。但是,讲师可能希望每个字符在不同的行上,并且给了你错误的示例输出。你得猜猜他真正想要的是什么。如果您想让您的代码防弹,您可能需要在 System.out 上进行同步,如以下内容所述:
Synchronization and System.out.println
但是,您的讲师可能不关心这个练习的问题。
最后,应进行以下更正以修复代码中的其他不良做法:
不应使用通配符导入,因为您没有使用并发包中的大量 classes。在我看来,永远不应该使用通配符导入。通配符导入会导致难以查看 classes 的来源,从而损害代码的易读性,而易读性是代码最重要的方面。
有意义的常量,比如这段代码中的“10”、“26”、“36”、“37”,不要写成字面量,而是应该使用定义好的常量,例如
static final int NUMBER_OF_DIGITS = 10;
。然后代码本身可以使用符号NUMBER_OF_DIGITS
,使其更易读且更易于维护,因为您可以轻松更改常量的值 - 例如,如果要将代码转换为八进制,则更改为 8 - 无需担心您会错过一些常数。有逻辑关系的常量尤其不要写成字面量。在这种情况下,即使
static final int NUMBER_OF_CHARACTERS = 36
也不是好的做法;应该是static final int NUMBER_OF_CHARACTERS = NUMBER_OF_DIGITS + NUMBER_OF_LETTERS;
,逻辑和数值关系清楚
您是否真的想在您上交的作业中进行这些更正取决于您认为教师对给予良好更正的反应。