Руководство по Java Core. Многопоточность.

Язык программирования Java поддерживает многопоточность. Это означает, что мы можем разрабатывать многопоточные приложения.

Многопоточные приложения – это программы, которые имеют два и более потока, выполняющиеся одновременно. Т.е. одна часть программы выполняет одну задачу, а вторая часть – другую.

Суть этого механизма заключается в том, что мы создаём два и более потока, которые работают параллельно.


Жизненный цикл потока

Давайте подробно рассмотрим жизненный цикл потока.

  • Новый (New)
    Созданный поток находится  в состоянии новый и находится в нём до начала его работы.
  • Исполняемый (Runnable)
    После того как поток начал свою работу он становится исполняемым (runnable). Это означает, что данный поток выполняет какую-то работу.
  • Ожидающий (Waiting)
    Временами поток не может выполнять свою работу, например ожидает другой поток, который выполняет определённые вычисления. Поток находится в состоянии ожидания до момента, когда получит определённый сигнал, например о том, что все необходимые вычисления выполнены.
  • Временно ожидающий (Timed waiting)
    Мы можем задать потоку определённое время ожидания, по истечении которого он продолжит свою работу.
  • Остановленный (Terminated)
    После того как поток выполнит свою работу, он преходит в состояние остановленный (прекращённый).

Свойства потока

Каждый поток имеет определённый приоритет, который помогает операционной системе (далее – ОС) определять, в каком именно порядке имеющиеся потоки должны быть запущены.

Приоритет потоков варьируется от минимального (MIN_PRIORITY – 1), до максимального (MAX_PRIORITY – 10). По умолчанию, все потоки создаются с нормальным приоритетом (NORM_PRIORITY – 5).

Потоки с более высоким приоритетом являются более важными для программы и, при прочих равных, именно они будут запущены раньше других.


Создание потока с помощью интерфейса Runnable

Для понимания того, как это работает на практике рассмотрим пример простого приложения.

Стоит отметить, что класс, в котором мы хотим использовать многопоточность, должен имплементировать интерфейс Runnable.

Пример:

Класс ThreadByRunnable


public class ThreadByRunnable implements Runnable {
    private Thread thread;
    private String threadName;

    public ThreadByRunnable(String threadName) {
        this.threadName = threadName;
        System.out.println("Thread " + threadName + " created successfully.");
    }

    @Override
    public void run() {
        System.out.println("Thread " + threadName + " is running...");

        try {


            for (int i = 1; i <= 5; i++) {
                System.out.println("Thread " + threadName + " " + i);
                Thread.sleep(100);
            }
        }catch (InterruptedException e){
            System.out.println("Thread " + threadName + " interrupted.");
            e.printStackTrace();
        }

        System.out.println("Leaving thread " + threadName);
    }

    public void start() {
        System.out.println("Thread " + threadName + " is started successfully.");
        if(thread == null){
         thread = new Thread(this, threadName);
            thread.start();
        }
    }
}

Класс MultithreadingDemo


public class MultithreadingDemo {
    public static void main(String[] args) {
        ThreadByRunnable threadOne = new ThreadByRunnable("ThreadOne");
        ThreadByRunnable threadTwo = new ThreadByRunnable("ThreadTwo");

        threadOne.start();
        threadTwo.start();
    }
}

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


/*Some System Messages*/

Thread ThreadOne created successfully.
Thread ThreadTwo created successfully.
Thread ThreadOne is started successfully.
Thread ThreadTwo is started successfully.
Thread ThreadOne is running...
Thread ThreadOne 1
Thread ThreadTwo is running...
Thread ThreadTwo 1
Thread ThreadOne 2
Thread ThreadTwo 2
Thread ThreadOne 3
Thread ThreadTwo 3
Thread ThreadOne 4
Thread ThreadTwo 4
Thread ThreadOne 5
Thread ThreadTwo 5
Leaving thread ThreadOne
Leaving thread ThreadTwo

Создание потока с помощью класса Thread

Второй способ создания потока – наследование класса Thread. Этот способ даёт нам больше гибкости при работе с потоками, благодаря методам класса Thread.

Рассмотрим пример простого приложения.

Пример:

Класс ThreadByExtending


public class ThreadByExtending extends Thread{
    private Thread thread;
    private String threadName;

    public ThreadByExtending(String threadName) {
        this.threadName = threadName;
        System.out.println("Thread " + threadName + " created successfully.");
    }

    public void run(){
        System.out.println("Thread " +  threadName + " is running...");

        try {
            for(int i = 1;i<=5; i++){
                System.out.println("Thread " + threadName + " " + i);
                Thread.sleep(100);
            }
        }catch (InterruptedException e){
            System.out.println("Thread " + threadName + " is interrupted.");
            e.printStackTrace();
        }
        System.out.println("Leaving thread " + threadName);
    }

    public void start(){
        System.out.println("Thread " + threadName + " is started successfully.");
        if(thread == null){
            thread = new Thread(this,threadName);
            thread.start();
        }
    }
}

Класс ThreadByExtendingDemo


public class ThreadByExtendingDemo {
    public static void main(String[] args) {
        ThreadByExtending threadOne = new ThreadByExtending("Thread One");
        ThreadByExtending threadTwo = new ThreadByExtending("Thread Two");

        threadOne.start();
        threadTwo.start();
    }
}

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


/*Some System Messages*/

Thread Thread One created successfully.
Thread Thread Two created successfully.
Thread Thread One is started successfully.
Thread Thread Two is started successfully.
Thread Thread One is running...
Thread Thread One 1
Thread Thread Two is running...
Thread Thread Two 1
Thread Thread One 2
Thread Thread Two 2
Thread Thread One 3
Thread Thread Two 3
Thread Thread One 4
Thread Thread Two 4
Thread Thread One 5
Thread Thread Two 5
Leaving thread Thread One
Leaving thread Thread Two

С конструкторами и методами класса Thread вы можете ознакомиться в официальной документации.


Синхронизация потока

Когда мы работаем с несколькими потоками, может сложиться ситуация, при которой, несколько потоков пытаются получить доступ к одному и тому же объекту, что может привести  к непредсказуемым результатам.

Например, несколько потоком пытаются внести изменения в один  и тот же текстовый файл. В результате этого данные могут быть повреждены, так как один поток пытается записать данные “поверх” данных второго потока.

Для предотвращения таких ситуаций применяется синхронизация.

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

Для понимания того, как то работает на практике, рассмотрим пример простого приложения.

ССЫЛКА НА ПРИМЕР.


Основные операции с потоками

Язык программирования Java обеспечивает полный контроль над многопоточными приложениями. Благодаря этому мы можем разрабатывать многопоточные программы, которые ведут себя именно так, как нам это необходимо.

Рассмотрим пример простого приложения.

ССЫЛКА НА ПРИМЕР.


Взаимодействие потоков

В реальных приложениях мы часто сталкиваемся с ситуацией, когда два потока обмениваются какой-либо информацией.

Для обеспечения этого процесса применяются такие методы, как

  • public void wait()
    Этот метод переводит поток в режим ожидания до момента вызова метода notify().
  • public void notify()
    Этот метод переводит один поток из режима ожидания в активный режим.
  • public void notifyAll()
    Этот метод переводит все потоки, которые находятся в режиме ожидания в активный режим.

Все эти методы реализованы, как финальные методы класса Object, что означает их доступность для любого класса в языке программирования Java. Все эти методы могут быть применены только в контексте синхронизации.

Для понимания того, как это работает на практике, рассмотрим пример простого приложения.

ССЫЛКА НА ПРИМЕР.


Взаимная блокировка потоков

Взаимная блокировка – это ситуация, при которой два или более потоков заблокированы навсегда, ожидая друг друга.

Для понимания того, как это работает на практике рассмотрим пример простого приложения.

ССЫЛКА НАПРИМЕР.


В этом уроке мы изучили основы многопоточности в языке программирования Java и рассмотрели простые примеры с её применением.

В следующем уроке мы изучим апплеты в языке программирования Java.