I once wrote a program to calculate Pascal's triangle limited only by memory. I recently looked back and was disturbed by how ugly it is. I can tell that there are some bad practices for lack of better options known to me.import java.math.BigInteger;public class Pascal {    private final int numRows; //The number of rows that the program should calculate    private final int numThreads = Runtime.getRuntime().availableProcessors(); //The number of cores that we can spawn threads for    private final Thread[] workerThread = new Thread[numThreads]; //The threads that will do the calculations    private final boolean[] threadRunning = new boolean[numThreads]; //A reference for the threads to synchronize    private final boolean[] stopThread = new boolean[numThreads]; //A reference for the threads to stop    private final BigInteger[][] pascal; //The actual triangle    private int i; //This is just a terrible workaround for the 'threadRun' to be able to access index loops    private static final BigInteger one = new BigInteger(1); //BigInteger representing 1    private BigInteger maxNumber = null; //The maximum number in the triangle    private final Runnable threadRun = () -> { //The thread code to speed up the process up by as many cpus as there are        final int threadId = Pascal.this.i; //Index loop is the thread id when the thread is started        //System.out.println(i);        for(;;) {            if(Pascal.this.stopThread[threadId]) return; //Stop if requested            while(!Pascal.this.threadRunning[threadId]) { //If the thread is paused                if(Pascal.this.stopThread[threadId]) return; //Stop if requested                try {                    Thread.sleep(1); //Or sleep until it's running again                }                catch(InterruptedException ex) {                    if(Pascal.this.stopThread[threadId]) return; //Don't stop this thread! We still need it! Unless it is told to stop.                }            }            for(int a = threadId + 1; a < Pascal.this.i; a += numThreads) { //Calculate this thread's share of the row                Pascal.this.pascal[Pascal.this.i][a] = Pascal.this.pascal[Pascal.this.i-1][a-1].add(Pascal.this.pascal[Pascal.this.i-1][a]);            }            Pascal.this.threadRunning[threadId] = false; //This thread is done, so notify the main thread and wait for furuther instructions        }    };    public Pascal(int rows) {        pascal = new BigInteger[rows][]; //Each row has variable length        numRows = rows;    }    public void calculate(boolean print) {        //Start alll the threads in idle mode        for(i = 0; i < numThreads; i++) {            threadRunning[i] = false;            stopThread[i] = false;            workerThread[i] = new Thread(threadRun);            workerThread[i].start();            //Wait for the thread to spawn            try {                Thread.sleep(50);            }            catch(InterruptedException ex) {}        }        //Print the first two lines        if(print) {            System.out.println(1);            System.out.println(1 1);        }        //Initialize the first two rows        pascal[0] = new BigInteger[] { one };        pascal[1] = new BigInteger[] { one, one };        //Iterate through the rows        for(i = 2; i < numRows; i++) {            //Can't have the worker threads accessing the pascal at the same time as the main thread            synchronized(pascal) {                //This is a triangle array, so the row length is the row number                pascal[i] = new BigInteger[i + 1];                synchronized(pascal[i]) {                    //Both ends of the triangle are one by definition                    pascal[i][0] = one;                    pascal[i][i] = one;                }            }            //Let the theads compute!            for(int j = 0; j < numThreads; j++) {                threadRunning[j] = true;            }            //Wait for the threads to finish computing            while(!allFalse(threadRunning)) {}            //Print the row if desired            if(print) {                for(int j = 0; j <= i; j++) {                    System.out.print(pascal[i][j] +  );                }                System.out.println();            }            //Free memory that is no longer needed            pascal[i - 1] = null;            //The biggest number in the row is that in the middle            maxNumber = pascal[i][i/2];        }        //Stop all of the threads when the computation is done        for(int i = 0; i < numThreads; i++) {            stopThread[i] = true;        }    }    public BigInteger getMaxNumber() {        return maxNumber;    }    public static void main(String[] args) {        int numRows = 10;        if(args.length > 0) { //Check if the user wants to specify a row count other than 10            try {                numRows = Integer.parseInt(args[0]);            }            catch(NumberFormatException ex) {                System.out.println(Please enter a valid number.);                System.exit(1);            }        }        numRows++; //To account for one less row being calculated than should be        Pascal pascal = new Pascal(numRows);        pascal.calculate(true); //actually print the triangle        System.out.println(pascal.getMaxNumber());    }    //Checks the array to see if all the elements are false    public static boolean allFalse(boolean[] array) {        for(boolean b : array) if(b) return false;        return true;    }}For example, there has got to be a better way for interthread communication. I am looking for best Java practices here that makes the code easy to read but also if there are performance or memory usage improvements, that is also welcome.I have tried to comment the code as best as I can.
Multithreaded Pascal's Triangle Calculator
java