Java Practice and Note: I/O and Multi-threading

I followed a guide to learn IO and Multi-threading in Java at this website, but it’s in Chinese. I’ll write down some notes of the labs I did.

I/O

The first first task is: Given a directory, traverse all files (including subfolders) in this directory and return the array of the filenames.

import java.io.*;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;

public class FileTraverser {
    private File f;
    // save pathnames of all files including subfolders
    private LinkedList<File> fileListSubSave;
    // save pathnames of all files not including subfolders
    private LinkedList<File> fileListSave;
    // a list to save pathnames of all files that 
	// contain the given string including subfolders
    private LinkedList<String> fileConList;

    public FileTraverser(String fileName) {
        f = new File(fileName);
        getListSubFile();
        getListFile();
    }

    // get the filename array not including subfolders
    public String[] getList() {
        return f.list();
    }

    // get the filename array including subfolders
    public String[] getListSub() {
        String[] listSub = new String[fileListSubSave.size()];
        int i = 0;
        for (File file : fileListSubSave) {
            listSub[i] = file.getName();
            i++;
        }
        return listSub;
    }

    private void getListFile() {
        fileListSave = new LinkedList<>();
        Collections.addAll(fileListSave, Objects.requireNonNull(f.listFiles()));
    }

    // use recursion to get a list of all File(including subfolders)
	private void getListSubFile() {
        fileListSubSave = getListSubFileHelper(this.f);
    }

	private LinkedList<File> getListSubFileHelper(File thisFile) {
        if (thisFile.listFiles() == null){
            return new LinkedList<>();
        }
        LinkedList<File> fileListSub = new LinkedList<>();
        for (File file : thisFile.listFiles()) {
            fileListSub.add(file);
            if (file.isDirectory()) {
                fileListSub.addAll(getListSubFileHelper(file));
            }
        }
        return fileListSub;
    }
}

Next find out the biggest file in the directory and return the filename:

// find out the biggest file in the directory not including subfolders
public String getBiggest() {
    return getBiggestFromList(fileListSubSave);
}

// find out the biggest file in the directory including subfolders
public String getBiggestSub() {
    return getBiggestFromList(fileListSave);
}

private String getBiggestFromList(Iterable<File> listFile) {
    if (listFile == null) {
        throw new java.lang.NullPointerException("There is no file in the dictionary");
    }
    String biggestName = "";
    long biggestLen = Long.MIN_VALUE;
    for (File file : listFile) {
        if (file.isFile() && file.length() > biggestLen) {
            biggestName = file.getName();
            biggestLen = file.length();
        }
    }
    return biggestName;
}

Then find out those files whose contents include a string(ie. “con”), and print their pathname out(using BufferedReader):

public List<String> findFilesContainString(String s) {
    fileConList = new LinkedList<>();
    for (File file : fileListSubSave) {
        if (file.isFile() && isContain(file, s)) {
            fileConList.add(file.getPath());
        }
    }
    return fileConList;
}

private boolean isContain(File file, String s) {
    try (FileReader fr = new FileReader(file);
         BufferedReader br = new BufferedReader(fr);) {
        String line;
        while (null != (line = br.readLine())) {
            if (line.contains(s)) {
                return true;
            }
        }
        return false;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return false;
}

Multithreading

Change last exercise to multi-thread to find file content: The idea of the original exercise is to traverse all files, and when the search is completed, traverse the next file. Now adjust this idea through multi-threading: traverse all files, create a thread to find the content of this file, don’t have to wait for the end of this thread, and continue to traverse the next file.

public List<String> findFilesContainStringMultiThread(String s) {
    fileConList = new LinkedList<>();
    if (fileListSubSave.size() == 1) {
        return findFilesContainString(s);
    }
    for (File file : fileListSubSave) {
        if (file.isFile()) {
            new Thread(new isContain(file, s)).start();
        }
    }
    return fileConList;
}

// implements Runnable class
private class isContain implements Runnable{
    File file;
    String s;

    isContain(File file, String s) {
        this.file = file;
        this.s = s;
    }

    @Override
    public void run() {
        if (isContain(file, s)) {
//              System.out.println(file.getPath());
            fileConList.add(file.getPath());
        }
    }
}

But this doesn’t seem to work. Every time I run it, it gives different outputs. Sometimes one file, sometimes two files. I don’t know the reason now. But I guess it’s because the some threads returned fileConListwhen other threads were finding the String and add filename to the list. I need to learn more concepts of multithreading to figure this out.

Concurrency

The synchronization problem of multithreading refers to the problem that may be caused when multiple threads modify a data at the same time, also known as concurrency problem.

The solution is to use synchronizedkeyword. When a thread accesses a synchronized(this) code block in an object, other threads trying to access the object will be blocked.

public List<String> findFilesContainStringMultiThread(String s) {
    if (fileListSubSave.size() == 1) {
        return findFilesContainString(s);
    }
    isContain ic = new isContain(s);
    for (File file : fileListSubSave) {
        if (file.isFile()) {
            new Thread() {
                public void run(){
                    ic.checkIfAdd(file);
                }
            }.start();
        }
    }
    return ic.fileConList;
}

// create a class to represent the process
private class isContain{
    LinkedList<String> fileConList;
    String s;

    isContain(String s) {
        this.s = s;
        this.fileConList = new LinkedList<>();
    }

    synchronized void checkIfAdd(File file) {
        if (file.isFile() && isContain(file, s)) {
            fileConList.add(file.getPath());
        }
    }
}

Change code to this but it still doesn’t work, maybe I’ll come back later……😩

Practice: Producer and Consumer

Producer consumer problem is a very typical thread interaction problem.

  1. Use stack to store data
    • 1.1 transform stack to support thread safety
    • 1.2 handle the boundary operation of the stack. When the data in the stack is 0, the thread accessing the pull will wait. When the data in the stack is 200, the thread accessing the push will wait
  2. Provide a producer thread class to push random uppercase characters into the stack
  3. Provide a consumer thread class to pop up characters from the stack and print them to the console
  4. Provide a test class to run two producer and three consumer threads at the same time. The results are similar to the following:

If the methods of a class are all modified with synchronized, then the class is called a thread-safe class.

import java.util.Stack;

class MyStack<T> extends Stack<T> {

    @Override
    synchronized public T push(T t) {
        // when dealing with multiple threads,
        // need to repeatedly check the status
        // to avoid being notified by other push
        while (size() >= 200) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        T tt = super.push(t);
        this.notify();
        return tt;
    }

    @Override
    synchronized public T pop() {
        // same as push
        while (this.isEmpty()) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        T tt = super.pop();
        this.notify();
        return tt;
    }

    @Override
    synchronized public T peek() {
        return super.peek();
    }

    @Override
    synchronized public int size() {
        return super.size();
    }
}

When dealing with multiple threads, need to use while to repeatedly check the size of stack to avoid being notified by other push. For pop, it is the same.
Producer thread:

public class Producer extends Thread{
    private MyStack<Character> stack;

    public Producer(MyStack<Character> stack,String name){
        super(name);
        this.stack =stack;
    }

    public void run(){
        while(true){
            char c = (char)(Math.random()*26+'A');
            System.out.println(this.getName() + " produces: " + stack.push(c));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Consumer thread:

public class Consumer extends Thread{
    private MyStack<Character> stack;

    public Consumer(MyStack<Character> stack,String name){
        super(name);
        this.stack =stack;
    }

    public void run(){
        while(true){
            System.out.println(this.getName() + " consumes: " + stack.pop());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Test class:

public class ProducerConsumerTestNew {

    public static void main(String[] args) {
        MyStack<Character> stack = new MyStack<>();
        new Producer(stack, "Producer1").start();
        new Producer(stack, "Producer2").start();
        new Consumer(stack, "Consumer1").start();
        new Consumer(stack, "Consumer2").start();
        new Consumer(stack, "Consumer3").start();
    }
}

So I guess the solution to the last concurrency problem is clear now, which is to make fileConList(the list to save pathnames of all files that contain the given string) to be a Thread Safe list. So that when mutiple are adding filenames to it at the same time, they will wait for each other to finish.

This gives me a lot of thought. When you encounter a problem that seems to be strange and hard to resolve, do not be upset. You can put it down for now and continue. Once you learn more knowledge and concepts, the problem will solve itself. This is a very interesting process and I always get a stronge sense of satisfaction everytime.