1

In my program, I want to create multiple threads in one of the methods where each thread has to run a specific method with a given input. Using Runnable, I have written this snippet.

class myClass {
  public myClass() { }
  public void doProcess() {
    List< String >[] ls;
    ls = new List[2];  // two lists in one array
    ls[0].add("1");  ls[0].add("2");  ls[0].add("3");
    ls[1].add("4");  ls[1].add("5");  ls[1].add("6");

    // create two threads 
    Runnable[] t = new Runnable[2]; 
    for (int i = 0; i < 2; i++) {
      t[ i ] = new Runnable() {
        public void run() {
          pleasePrint( ls[i] );
        }
      };
      new Thread( t[i] ).start();
    }
  }
  void pleasePrint( List< String > ss )
  {
    for (int i = 0; i < ss.size(); i++) {
      System.out.print(ss.get(i)); // print the elements of one list
    }
  }
}

public class Threadtest {
  public static void main(String[] args) {
    myClass mc = new myClass();
    mc.doProcess();
  }
}

Please note, my big code looks like this. I mean in one method, doProcess(), I create an array of lists and put items in it. Then I want to create threads and pass each list to a method. It is possible to define the array and lists as private class members. But, I want to do that in this way.

Everything seems to be normal, however, I get this error at calling pleasePrint():

error: local variables referenced from an inner class must be final or effectively final
      pleasePrint( ls[i] );

How can I fix that?

mahmood
  • 23,197
  • 49
  • 147
  • 242

2 Answers2

2

The reason you are getting this error is straightforward and clearly mentioned - local variables referenced from an inner class must be final or effectively final. This is, in turn, because, the language specification says so.

Quoting Guy Steele here:

Actually, the prototype implementation did allow non-final variables to be referenced from within inner classes. There was an outcry from users, complaining that they did not want this! The reason was interesting: in order to support such variables, it was necessary to heap-allocate them, and (at that time, at least) the average Java programmer was still pretty skittish about heap allocation and garbage collection and all that. They disapproved of the language performing heap allocation "under the table" when there was no occurrence of the "new" keyword in sight.

As far as your implementation goes, instead of using an array of list, I'd rather use a list of lists.

private final List<List<String>> mainList = new ArrayList<>();

You can create new lists and insert them into the main list in the constructor depending on the number of lists you want.

public ListOfLists(int noOfLists) {
    this.noOfLists = noOfLists;
    for (int i = 0; i < noOfLists; i++) {
        mainList.add(new ArrayList<>());
    }
}

You can then change your doProcess() method as follows:

public void doProcess() {
    for (int i = 0; i < noOfLists; i++) {
        final int index = i;
        // Using Lambda Expression as it is much cleaner
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName());
            pleasePrint(mainList.get(index));   // Pass each list for printing
        }).start();
    }
}

Note: I used an instance variable named noOfLists to (as the name suggests) store the number of lists I need. Something as follows:

private final int noOfLists;

To populate the list, you could do:

mainList.get(0).add("1");
mainList.get(0).add("2");
mainList.get(0).add("3");
mainList.get(1).add("4");
mainList.get(1).add("5");
mainList.get(1).add("6");
// And so on...

And you'll get the output something as:

Thread-0
1
2
3
Thread-1
4
5
6

Hope this helps :)

Bilesh Ganguly
  • 3,792
  • 3
  • 36
  • 58
  • I think it is not allowed to define `noOfLists` in such a way and then assign a value with `this` reference. Is it OK to remove the `final` or it is mandatory? – mahmood May 20 '17 at 19:46
  • The list has to be `final` if it is gonna be used in the `run()` – Bilesh Ganguly May 21 '17 at 06:24
  • Is it normal to see different outputs due to the multiple threads? I see `Thread-0` then `Thread-1` then `1 2 3 4 5 6` each on a line. – mahmood May 21 '17 at 14:00
  • @mahmood Yes. Since, two threads are executing, the order in which those messages are printed might not be same. – Bilesh Ganguly May 21 '17 at 14:05
  • For large works, while the threads are in progress, I see next `printf` in my program (the printf statements after the thread loop). Is there any way to wait for the threads to finish and then continue the code? – mahmood May 22 '17 at 18:30
  • @mahmood If you want one thread to complete its execution at a time, then You can use `synchronized` (or Locks). But that basically boils down to a single thread doing its job at a time. – Bilesh Ganguly May 22 '17 at 18:38
  • There is a method described at https://stackoverflow.com/a/1252202/859227 but that is applicable when threads are defined before. Your code seems to use lambda expression for each thread. So outside of the loop, the `thread[i]` is undefined. – mahmood May 22 '17 at 18:45
  • Oh! Yeah, use ExecutorService and submit your threads to it. You can check if the executor terminated or not and do your processing accordingly. – Bilesh Ganguly May 22 '17 at 18:46
  • You could create a loop `while(!executorService.isTerminated()){}`. This will wait till all threads submitted to the pool execute. Then you can do your processing. – Bilesh Ganguly May 22 '17 at 18:50
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/144865/discussion-between-mahmood-and-bilesh-ganguly). – mahmood May 22 '17 at 19:36
1

First to that, you will get a NullPointerException here:

ls[0].add("1");  ls[0].add("2");  ls[0].add("3");
ls[1].add("4");  ls[1].add("5");  ls[1].add("6");

Before, yo must instantiate the lists:

ls[0] = new ArrayList<>();
ls[1] = new ArrayList<>();

About the compiler error, try to define the array as final. Change:

List< String >[] ls;
ls = new List[2];  // two lists in one array

By:

final List< String >[] ls = new List[2];  // two lists in one array

This is because you can't access to non-final (or effectively final) variables from a local class.

'ls' is effectively final but probably, since you have defined it in two lines, the compiler is not able to notice that.

Mario
  • 1,661
  • 13
  • 22
  • Why it is considered as final? – mahmood May 19 '17 at 11:54
  • Still I get the same error. I think it is complaining that `ls[i]` is final and I didn't declare that. – mahmood May 19 '17 at 11:55
  • @mahmood, A variable is _final_ if it is declared with the `final` keyword. A variable that is not declared `final` is _effectively final_ if it never appears on the left hand side of an assignment expression. (Note: that when you give it an initial value in the same statement that declares it, that's not technically an "assignment", it's called "initialization" instead.) – Solomon Slow May 19 '17 at 13:23
  • @mahmood, Java permits an anonymous inner class expression to _capture_ the values of final and effectively final variables from enclosing scopes, but it won't let you capture any other variables. That's just how the inventors of Java decided it should work. – Solomon Slow May 19 '17 at 13:26
  • I also tried the suggestion at http://stackoverflow.com/a/32707624/859227 however, I get the same error. I don't understand why it is complaining that. – mahmood May 19 '17 at 13:32