0

I have a question about the following example. We have a main class which has to download many files from web server and DownloadTask who does the job. There is an object saveTo in Main.java who is overwriten every loop. This object is in that loop passed to DownloadTask as reference in constructor. The DownloadTask takes this object and does some work with it.

  1. Does object saveTo can be overwritten in for loop in main method before is can be processed by DownloadTask?

    1.1 Why can / why can not. How does object creation in loop works?

  2. Does it make a difference if this:

...
for (File fileToDownload: filesToDownload ) {
                File saveTo = new File("C:\\temp\\"+fileToDownload.getName());
                ...

would change into this:

...
File saveTo;
for (File fileToDownload: filesToDownload ) {
                saveTo = new File("C:\\temp\\"+fileToDownload.getName());
                ...

Main.java:

public static void main(String[] args) {
            ArrayList<File> filesToDownload = new ArrayList<>();
            filesToDownload.add(new File("File1"));
            filesToDownload.add(new File("File2"));
            filesToDownload.add(new File("File3"));
            ...
            filesToDownload.add(new File("File100"));

            ExecutorService pool = Executors.newFixedThreadPool(2);
            CompletionService<Boolean> completionService = new ExecutorCompletionService<>(pool);
            ArrayList<Future<Boolean>> futures = new ArrayList<>();

            for (File fileToDownload: filesToDownload ) {
                File saveTo = new File("C:\\temp\\"+fileToDownload.getName());
                Future<Boolean> f = completionService.submit(new DownloadTask(new URL("http://1.1.1.1/" + fileToDownload.getName()), saveTo));
                futures.add(f);
            }

            for (int tasks=0;tasks<futures.size();tasks++) {
                completionService.take();
            }        
}

And this is DownloadTask.java:

public class DownloadTask implements Callable<Boolean> {

                private URL fileURL;
                private File saveTo;

                public DownloadTask(URL fileURL, File saveTo) {
                    this.fileURL = fileURL;
                    this.saveTo = saveTo;
                }

                private void downloadFile(URL fileURL, File saveTo) throws IOException {
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    ReadableByteChannel readableByteChannel = Channels.newChannel(fileURL.openStream());

                    FileOutputStream fileOutputStream = new FileOutputStream(saveTo);
                    fileOutputStream.getChannel().transferFrom(readableByteChannel, 0, Long.MAX_VALUE);

                    fileOutputStream.close();
                    readableByteChannel.close();
                }

                @Override
                public Boolean call() throws IOException {
                    downloadFile(fileURL, saveTo);
                    return true;
                }
            }

Thank you!

1
  • create objects for the scope they're intended to. Do you need saveTo outside the loop? If no, then option 1 is the good one. Don't be scared about the new object references. The JVM will handle it. Commented Feb 25, 2020 at 14:12

2 Answers 2

2

How does object creation in loop works?

It works in exactly the same way that it works anywhere else. Each evaluation of the expression new File(...) causes a new instance of the File class to be allocated from the heap, it causes a constructor to be called with the ... argument, and it returns a reference to the new File instance.

Your code example creates a new File instance on each iteration of the loop, and stores a reference to it in the saveTo local variable, overwriting the previous value of saveTo. That assignment to saveTo does not have any effect on the new File instance, and it does not have any effect on the File instance to which saveTo previously referred.

The only place where saveTo is used is on the next line:

...f = completionService.submit(new DownloadTask(..., saveTo));

So, the only effect of the assignment to saveTo is to determine which File instance will be provided to each new DownloadTask that your program creates.

Does it make a difference if [the declaration of saveTo is moved out of the loop?]

It won't make any difference to how your program runs. The compiler will emit the same code in either case. It might make a difference to how easy it is for other programmers to read your code though. When somebody sees that saveTo is declared inside the for loop body, then they don't need to waste any time looking to see where else it might be used. If you declare it inside the loop, then Java won't allow you to use it outside the loop.

Sign up to request clarification or add additional context in comments.

2 Comments

I'm not sure if I understand you correctly. The variable saveTo which is passed to another thread in DownloadTask constructor cannot be altered in previous thread by new File("C:\\temp\\"+fileToDownload.getName()); in for cycle. Is that all right? That was the main point of my 1. question. I just forgot the question mark.
@user2014521, The variable is not passed. Java does not provide any way for one thread to access or modify the local variables of another thread. What gets passed is the reference to a File instance on the heap. If it makes any sense to compare Java to C or C++, then every non-primitive Java variable works like a C or C++ pointer variable. (Except, you can't do pointer arithmetic in Java.)
1

Your code is thread-safe (in other words it's fine). The problem with thread safety would occur in the following example:

public class MyClass {
  private int counter = 0;

  public int myMethod() {
    ...
    counter++;
    ...
    return counter;
  }
}

Imagine that you create a single instance of MyClass and allow some code in different threads to invoke myMethod() simultaneously. Consider that 2 threads run it. both increment your counter (which is shared) by one before each one returns. Each one expects the return to be 1 but it will be 2. In your case, there are no resources outside the methods. So different threads do not interfere with each other. In order to fix the example above you will need to do change it to this:

  public class MyClass {

  public int myMethod(int counter) {
    ...
    counter++;
    ...
    return counter;
  }
}

In this case in each thread there will be a separate instance of counter - not shared. And it will work as expected. Thus thread-safe. Note that this is a very simplistic example, and thread-safety is a very complex and thorny issue.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.