1

我在一个 java 程序中创建了三个线程。一个是主程序,另外两个是扩展 Thread 的两个类。主线程代表机器的控制器。另一个线程是执行器,第三个是传感器。控制器在其自己的类中设置变量,由执行器线程读取。执行器根据指令执行某些动作并更新自己的内部变量。这些依次由传感器线程读取,该线程读取执行器变量(代表现实世界的动作)并设置其自己的内部变量,这些变量又由控制器读取,我们已经完成了一个完整的循环。然后控制器根据新感知的世界等设置变量。

执行器处于一个永久循环中,每个循环中休眠 100 毫秒。传感器也处于一个永久循环中,每个循环休眠 20 毫秒。

该系统几乎可以工作。主循环将错过传感器的更新,除非我也为其添加睡眠。我现在的问题是为什么会这样?即使 sleep(0) 也会使系统正常工作。我已将语句放在 performJob(Job input) while 循环中。没有 sleep 调用的 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() 调用替换睡眠调用,并且程序将运行。

4

3 回答 3

1

这里发生的很可能是内存一致性错误。当控制器类设置内部控制变量然后进入循环等待传感器时,它很可能会阻止执行器和传感器类正确更新控制器看到的读数,从而阻止控制器看到正确的值。通过将同步语句添加到从另一个类读取的所有函数中,问题得到了解决。我只能推测睡眠调用让控制器线程进入了某种同步块,这让其他线程对变量的更改变得可见。

于 2012-09-16T17:26:46.283 回答
1

谷歌的“生产者消费者队列”。

除非您想要延迟、低效率和丢失数据,否则不要将 sleep() 用于线程间通信。有更好的机制可以避免 sleep() 并尝试直接从某些共享/锁定对象中读取有效数据。

如果您使用命令/请求/数据加载“通信”对象,将它们排队到其他线程并立即创建另一个通信对象以进行后续通信,您的线程间通信将快速且安全,没有 sleep() 延迟并且没有机会任何线程读取过时的数据或被另一个线程更改的数据。

于 2012-07-10T04:46:01.123 回答
0

您使用的是哪个 JVM?

作为快速解决方法,您可以为线程之间共享的字段设置 volatile。

还请查看演员的消息传递方法:http: //doc.akka.io/docs/akka/snapshot/java/untyped-actors.html

于 2012-07-10T04:44:47.457 回答