我在java程序中创建了三个线程。一个是主程序,其他是两个扩展Thread的类。主线程表示机器的控制器。另一个线程是执行器,第三个是传感器。控制器在其自己的类中设置变量,由执行器线程读取。执行器根据指令执行某些动作并更新其自身的内部变量。这些依次由传感器线程读取,传感器线程读取执行器变量(代表真实世界的动作)并设置其自身的内部变量,而这些内部变量又由控制器读取并且我们已经完整循环。然后控制器根据新感知的世界等设置变量。
执行器处于永久循环中,在每个循环中睡眠100毫秒。 传感器也处于永久循环中,每循环睡眠20ms。
系统几乎可以工作。主循环将错过传感器的更新,除非我也为它添加睡眠。我的问题是现在为什么会这样?即使睡眠(0)也可以使系统正常工作。我在while循环中将语句放在performJob(作业输入)中。没有睡眠调用的java主线程与同一个线程的行为有何不同? 并发对我来说是一个相当新的主题。
这是我正在使用的代码:
主要:
public class Main {
public static void main(String[] args) {
Controller Press = new Controller();
Press.processUnits(1); // no reset code at the moment, only use 1 at a time
Press.shutdownThreads();
}
}
控制器:
import java.util.LinkedList;
public class Controller extends Thread {
// Constants
static final int STATE_WAITING = 0;
static final int STATE_MOVE_ARMS = 1;
static final int STATE_MOVE_PRESS = 2;
static final int LOADINGARM = 2;
static final int UNLOADINGARM = 1;
static final int NOARM = 0;
static final boolean EXTEND = true;
static final boolean RETRACT = false;
private enum Jobs {
EXTENDRETRACT, ROTATE, MAGNETONOFF, PRESSLOWERRAISE
}
// Class variables
private int currentState;
// Component instructions
private int armChoice = 0;
private boolean bool = false; // on, up, extend / off, down, retract
private boolean[] rotInstr = {false, false}; // {rotate?, left true/right false}
private boolean errorHasOccurred = false;
private boolean pressDir = false;
private Sensors sense = null;
private Actuators act = null;
private LinkedList<Job> queue = null;
// Constructor
public Controller() {
act = new Actuators(0.0f, this);
sense = new Sensors();
act.start();
sense.start();
currentState = STATE_WAITING;
queue = new LinkedList<Job>();
}
// Methods
int[] getArmInstructions() {
int extret = (bool) ? 1 : 0;
int[] ret = {armChoice, extret};
return ret;
}
boolean[] getRotation() {
return rotInstr;
}
int getControllerState() {
return currentState;
}
boolean getPressDirection() {
return pressDir;
}
public boolean processUnits(int nAmount) {
if (run(nAmount)) {
System.out.println("Controller: All units have been processed successfully.");
return true;
} else { // procUnits returning false means something went wrong
System.out.println("Controller: An error has occured. The process cannot complete.");
return false;
}
}
/*
* This is the main run routine. Here the controller analyses its internal state and its sensors
* to determine what is happening. To control the arms and press, it sets variables, these symbolize
* the instructions that are sent to the actuators. The actuators run in a separate thread which constantly
* reads instructions from the controller and act accordingly. The sensors and actuators are dumb, they
* will only do what they are told, and if they malfunction it is up to the controller to detect dangers or
* malfunctions and either abort or correct.
*/
private boolean run(int nUnits) {
/*
Here depending on internal state, different actions will take place. The process uses a queue of jobs
to keep track of what to do, it reads a job, sets the variables and then waits until that job has finished
according to the sensors, it will listen to all the sensors to make sure the correct sequence of events is
taking place. The controller reads the sensor information from the sensor thread which runs on its own
similar to the controller.
In state waiting the controller is waiting for input, when given the thread cues up the appropriate sequence of jobs
and changes its internal state to state move arms
In Move arms, the controller is actively updating its variables as its jobs are performed,
In this state the jobs are, extend, retract and rotate, pickup and drop.
From this state it is possible to go to move press state once certain conditions apply
In Move Press state, the controller through its variables control the press, the arms must be out of the way!
In this state the jobs are press goes up or down. Pressing is taking place at the topmost position, middle position is for drop off
and lower is for pickup. When the press is considered done, the state reverts to move arms,
*/
//PSEUDO CODE:
//This routine being called means that there are units to be processed
//Make sure the controller is not in a blocking state, that is, it shut down previously due to errors
//dangers or malfunctions
//If all ok - ASSUMED SO AS COMPONENTS ARE FAULT FREE IN THIS VERSION
if (!errorHasOccurred) {
//retract arms
currentState = STATE_MOVE_ARMS;
queue.add(new Job(Jobs.EXTENDRETRACT, LOADINGARM, RETRACT));
queue.add(new Job(Jobs.EXTENDRETRACT, UNLOADINGARM, RETRACT));
performWork();
//System.out.println("Jobs added");
//while there are still units to process
for (;nUnits != 0; nUnits--) {
//move the press to lower position, for unloading
currentState = STATE_MOVE_PRESS;
//rotate to pickup area and pickup the metal, also pickup processed
currentState = STATE_MOVE_ARMS;
queue.add(new Job(Jobs.EXTENDRETRACT, LOADINGARM, EXTEND));
queue.add(new Job(Jobs.EXTENDRETRACT, UNLOADINGARM, EXTEND));
performWork();
//retract and rotate
queue.add(new Job(Jobs.EXTENDRETRACT, LOADINGARM, RETRACT));
queue.add(new Job(Jobs.EXTENDRETRACT, UNLOADINGARM, RETRACT));
performWork();
//state change, press moves to middle position
currentState = STATE_MOVE_PRESS;
//state change back, put the metal on the press, drop processed and pull arms back
currentState = STATE_MOVE_ARMS;
queue.add(new Job(Jobs.EXTENDRETRACT, LOADINGARM, EXTEND));
queue.add(new Job(Jobs.EXTENDRETRACT, UNLOADINGARM, EXTEND));
queue.add(new Job(Jobs.EXTENDRETRACT, LOADINGARM, RETRACT));
queue.add(new Job(Jobs.EXTENDRETRACT, UNLOADINGARM, RETRACT));
performWork();
//state change, press the metal in upper position
currentState = STATE_MOVE_PRESS;
//repeat until done
}
//unload final piece
//move the press to lower position for unload
//rotate and pickup processed piece
//drop it off at unloading and wait for more orders
currentState = STATE_WAITING;
}
return true;
}
private boolean performWork() {
while (!queue.isEmpty()) {
performJob(queue.removeFirst());
}
return true;
}
private boolean performJob(Job input) {
//The purpose of this function is to update the variables and wait until they are completed
// read in the job and check appropriate sensors
boolean[] data = sense.getSensorData(); // LExt, LRet, UlExt, UlRet
printBools(data);
int Instruction = input.Instruction;
boolean skipVars = false;
if (input.Job == Jobs.EXTENDRETRACT) {
if (currentState != STATE_MOVE_ARMS){
System.out.println("Wrong state in performJob. State is "+currentState+" expected "+STATE_MOVE_ARMS);
return false;
}
if ((Instruction == LOADINGARM) && (input.Bool)) skipVars = data[0];
if ((Instruction == LOADINGARM) && (!input.Bool)) skipVars = data[1];
if ((Instruction == UNLOADINGARM) && (input.Bool)) skipVars = data[2];
if ((Instruction == UNLOADINGARM) && (!input.Bool)) skipVars = data[3];
}
if (!skipVars) {
// if sensors not at intended values, update correct variables
System.out.println("Controller: Did not skip vars");
switch (input.Job) {
case EXTENDRETRACT:
armChoice = (Instruction == LOADINGARM) ? LOADINGARM : UNLOADINGARM;
bool = input.Bool;
break;
case ROTATE:
break;
case MAGNETONOFF:
break;
case PRESSLOWERRAISE:
break;
default:
System.out.println("Default called in performJob()");
break;
}
}
// listen to sensors until completed
boolean done = false;
System.out.println("Waiting for sensor data.");
//System.out.print("Instruction is "+Instruction+" and bool is "); if (input.Bool) System.out.print("true\n"); else System.out.print("false\n");
while (!done) {
data = sense.getSensorData();
// REMOVING THIS TRY STATEMENT BREAKS THE PROGRAM
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
System.out.println("Main thread couldn't sleep.");
}
// Check appropriate sensors
if (input.Job == Jobs.EXTENDRETRACT) {
if ((Instruction == LOADINGARM) && (input.Bool)) done = data[0];
if ((Instruction == LOADINGARM) && (!input.Bool)) done = data[1];
if ((Instruction == UNLOADINGARM) && (input.Bool)) done = data[2];
if ((Instruction == UNLOADINGARM) && (!input.Bool)) done = data[3];
}
}
// reset all variables
armChoice = 0;
bool = false;
// when done return
System.out.println("Finished "+input.Job);
return true;
}
public void shutdownThreads() {
sense.shutDown();
act.shutDown();
}
private class Job {
// Class variables
Jobs Job;
int Instruction;
boolean Bool; // used for directions, up/down, left/right, extend/retract
// Constructor
Job(Jobs newJob, int newInstruction, boolean dir) {
Job = newJob;
Instruction = newInstruction;
Bool = dir;
}
}
private void printBools(boolean[] input) {
System.out.println();
for (int i = 0; i < input.length; i++) {
if (input[i]) System.out.print("true "); else System.out.print("false ");
}
System.out.println();
}
}
致动器:
public class Actuators extends Thread {
// Constants
private final int ARM = 0, ROTATE = 0; // array indexes - which arm, rotate yes/no?
private final int DIR = 1, ROTDIR = 1; // array indexes - which direction ext/ret, rotate direction
private final int EXT = 1;//, RET = 0;
private final double ARM_SPEED = 5.0;
// Class variables
private Controller Owner = null;
private boolean run = true;
// Constructor
Actuators(float nPos, Controller Owner) {
Reality.changeAngle(nPos);
this.Owner = Owner;
}
// Methods
private void rotate(boolean dir) {
float nAngle = dir ? 0.1f : -0.1f;
Reality.changeAngle(nAngle);
}
public void run() {
while (run) {
try {
sleep(100);
} catch (InterruptedException e) {
System.out.println("Actuators couldn't sleep");
}
// read variables in controller
int nState = Owner.getControllerState();
if (nState == Controller.STATE_MOVE_ARMS) {
boolean[] rot = Owner.getRotation();
if (rot[ROTATE]) { // Rotation?
rotate(rot[ROTDIR]);
} else { // or arm extensions
int[] instr = Owner.getArmInstructions();
if (instr[ARM] != Controller.NOARM) { // 0 = no arm movement
//System.out.println("Actuator arm is "+instr[ARM]);
double dir = (instr[DIR] == EXT) ? ARM_SPEED : -ARM_SPEED; // 1 = extend, 0 = retract
Reality.changeArmLength(instr[ARM], dir);
}
}
}
}
}
void shutDown() {
run = false;
}
}
Reality是一个由静态字段和方法组成的类,由执行器写入并由传感器读取。
public class Reality {
// Constants
static private final double EXTEND_LIMIT = 100.0;
static private final double RETRACT_LIMIT = 0.0;
// Variables
private static float ArmsAngle = 0.0f;
// Read by Sensor
static double LoadingArmPos = 0.0;
static double UnloadingArmPos = 0.0;
// Methods
static void changeAngle(float newAngle) {
ArmsAngle = ArmsAngle + newAngle;
if ((ArmsAngle < 0.0f) || (ArmsAngle > 90.0f))
System.out.println("Reality: Unallowed Angle");
}
static void changeArmLength(int nArm, double dPos) { // true = extend, false = retract
switch (nArm) {
case Controller.LOADINGARM:
LoadingArmPos += dPos;
checkArmPos(LoadingArmPos);
break;
case Controller.UNLOADINGARM:
UnloadingArmPos += dPos;
checkArmPos(UnloadingArmPos);
break;
default:
System.out.println("Arm other than 2 (load) or 1 (unload) in changeArmLength in Reality");
break;
}
}
static float senseAngle() {
return ArmsAngle;
}
static private boolean checkArmPos(double dPos) {
// Allowed positions are 100.0 to 0.0
if ((dPos > EXTEND_LIMIT) || (dPos < RETRACT_LIMIT)) {
System.out.println("Arm position impossible in reality. Is "+dPos);
return true;
} else {
return false;
}
}
}
最后传感器:
public class Sensors extends Thread {
// Constants
private final double EXTENDED = 100.0;
private final double RETRACTED = 0.0;
private final double MARGIN = 0.1;
// Class Variables
private boolean run = true;
// Read by Controller
private boolean LoadingExtended = true;
private boolean LoadingRetracted = true;
private boolean UnloadingExtended = true;
private boolean UnloadingRetracted = true;
// Constructor
Sensors() {
LoadingExtended = false;
LoadingRetracted = true;
UnloadingExtended = false;
UnloadingRetracted = true;
}
// Methods
boolean senseLoadingExtended() {
return (Math.abs(Reality.LoadingArmPos - EXTENDED) < MARGIN);
}
boolean senseLoadingRetracted() {
return (Math.abs(Reality.LoadingArmPos - RETRACTED) < MARGIN);
}
boolean senseUnloadingExtended() {
return (Math.abs(Reality.UnloadingArmPos - EXTENDED) < MARGIN);
}
boolean senseUnloadingRetracted() {
return (Math.abs(Reality.UnloadingArmPos - RETRACTED) < MARGIN);
}
// called by Controller
boolean[] getSensorData() {
boolean[] ret = {LoadingExtended, LoadingRetracted, UnloadingExtended, UnloadingRetracted};
return ret;
}
// Sensor primary loop
public void run() {
while (run) {
try {
sleep(20);
}
catch (InterruptedException e) {
System.out.println("Sensors couldn't sleep");
}
LoadingExtended = senseLoadingExtended();
LoadingRetracted = senseLoadingRetracted();
UnloadingExtended = senseUnloadingExtended();
UnloadingRetracted = senseUnloadingRetracted();
}
}
void shutDown() {
run = false;
}
}
此版本中并未读取所有字段和函数。该程序是一个以前的单线程应用程序的重做工作,主要使用函数调用。为了便于阅读,我已经清理了一些代码。即使不是原始问题,也欢迎建设性的设计评论。有些东西真的很可疑。我通常不是一个迷信的编码器,但我可以用System.out.println()调用替换sleep调用,程序也可以工作。
答案 0 :(得分:1)
Google代表'生产者消费者队列'。
除非您想要延迟,低效和丢失数据,否则不要将sleep()用于线程间通信。有更好的机制可以避免sleep()并尝试直接从某个共享/锁定对象读取有效数据。
如果你使用命令/请求/数据加载'comms'对象,将它们排队到其他线程并立即创建另一个comms对象以进行后续通信,你的线程间通信将是快速和安全的,没有sleep()延迟并且任何线程都没有机会读取过时或被另一个线程更改的数据。
答案 1 :(得分:1)
这里发生的事情很可能是内存一致性错误。当控制器类设置内部控制变量然后进入等待传感器的循环时,它很可能阻止执行器和传感器类正确更新看到控制器的读数,从而阻止控制器看到正确的值。通过将synchronize语句添加到从另一个类读取的所有函数,问题得以解决。我只能推测睡眠调用让控制器线程进入某种同步块,让其他线程对变量的变化变得可见。
答案 2 :(得分:0)
您使用的是哪个JVM?
作为快速解决方法,您可以为线程之间共享的字段设置volatile。
另请注意演员的消息传递方式:http://doc.akka.io/docs/akka/snapshot/java/untyped-actors.html