Современные процессоры содержат много вычислительных ядер (в т.ч. в телефонах) и поэтому вычисления многих задач можно ускорить подразбивая их на несколько подзадач и исполняя эти части на разных ядрах процессора параллельно.

В Java все формулируется ввиде объектов - экземпляров классов. Поэтому для многопоточности в Java есть класс Thread, инстанцировав который возможно создать отдельный поток исполнения.

Чтобы создать Thread в Java надо написать такой код:

Thread t = new Thread(runnable);

Где аргумент runnable (переданный в конструктор) - Runnable объект. Это можно увидеть открыв документацию по конструктору Thread (достаточно напечатать new Thread, затем нажать Ctrl+Space и Ctrl+Q).

Чтобы понять, что такое Runnable, стоит посмотреть его объявление. Это делается через горячую кнопку “переход к классу/интерфейсу” - Ctrl+N и затем надо напечатать Runnable и нажать Enter. Как видно, кроме документации в Runnable определен лишь один метод:

public abstract void run();

Ключевое слово abstract говорит о том, что это является лишь декларацией/заявлением функции, но не ее определение. Иначе говоря, в этом месте Runnable заявляет (т.е. декларирует контракт) что у него обязательно есть функция с такой сигнатурой, но т.к. ее реализация может быть разной от случая к случаю - он сам ее не определяет. Не определяет он ее потому что Runnable - лишь интерфейс, т.е. обязательство на наличие такой функции.

Иначе говоря - если кто-то говорит, что он готов работать с Runnable (например функция принимает аргументом Runnable переменную, или как в случае с Thread - конструктор требует аргументом Runnable), то это означает, что подходит любой класс, который реализует контракт Runnable, т.е. обязуется соблюсти все, что декларировано в этом интерфейсе. Что в нашем случае означает реализацию метода public void run().

Соответственно, чтобы создать такой класс надо написать примерно следующее:

public class MyRunnable implements Runnable {

    public String name;
    public int n;
    
    public MyRunnable(String name, int n) {
        this.name = name;
        this.n = n;
    }

    public void run() {
        for (int i = 0; i < n; i++) {
            System.out.println(this.name + " - running " + i + "...");
        }
        System.out.println(this.name + " - finished!");
    }
}

И тогда создать поток можно от экземпляра нашего класса:

MyRunnable runnable = new MyRunnable("MyRunnable", 10);
Thread t = new Thread(runnable);

Но поток не начинает работу в момент своего создания. Чтобы поток действительно начал свое исполнение - надо его запустить - t.start(). Как можно прочитать в документации (курсор на start и Ctrl+Q) - поток начнет исполнение метода run() в отдельном потоке исполнения, т.е. параллельно.

Итого пример кода:

System.out.println("Starting spawning threads...");

for (int i = 0; i < 5; i++) {
    MyRunnable runnable = new MyRunnable("MyRunnable#" + i, 10);
    Thread t = new Thread(runnable);
    t.start();
}

System.out.println("All threads started!");