Освой Java играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

Потоки. Класс Thread и интерфейс Runnable

Интерфейс Runnable
Looper

В русской терминологии за термином Thread укрепился перевод "Поток". Хотя это слово также можно перевести как "Нить". Иногда в зарубежных учебных материалах понятие потока объясняется именно на нитях. Продолжим логический ряд - там где нити, там и клубок. А где клубок, там и кот. Сразу видно, что у переводчиков не было котов. Так и возникла путаница. Тем более что существуют другие потоки под термином Stream. Переводчики, вообще странный народ.

Когда запускается любое приложение, то начинает выполняться поток, называемый главным потоком (main). От него порождаются дочерние потоки. Главный поток, как правило, является последним потоком, завершающим выполнение программы.

Несмотря на то, что главный поток создаётся автоматически, им можно управлять через объект класса Thread. Для этого нужно вызвать метод currentThread(), после чего можно управлять потоком.

Класс Thread содержит несколько методов для управления потоками.

  • getName() - получить имя потока
  • getPriority() - получить приоритет потока
  • isAlive() - определить, выполняется ли поток
  • join() - ожидать завершение потока
  • run() - запуск потока. В нём пишите свой код
  • sleep() - приостановить поток на заданное время
  • start() - запустить поток

Получим информацию о главном потоке и поменяем его имя.


Thread mainThread = Thread.currentThread();
mInfoTextView.setText("Текущий поток: " + mainThread.getName());

// Меняем имя и выводим в текстовом поле
mainThread.setName("CatThread");
mInfoTextView.append("\nНовое имя потока: " + mainThread.getName());

Имя у главного потока по умолчанию main, которое мы заменили на CatThread.

Вызовем информацию о названии потока без указания метода.


Thread mainThread = Thread.currentThread();
mInfoTextView.setText("Текущий поток: " + mainThread);

В этом случае можно увидеть строчку Thread[main,5,main] - имя потока, его приоритет и имя его группы.

Создание собственного потока

Создать собственный поток не сложно. Достаточно наследоваться от класса Thread.

Объявим внутри нашего класса внутренний класс и вызовем его по щелчку, вызвав метод start().


public class MyThread extends Thread {
    public void run() {
        Log.d(TAG, "Mой поток запущен...");
    }
}

public void onClick(View view) {
    MyThread myThread = new MyThread();
    myThread.start();
}

Как вариант, перенести вызов метода start() в конструктор.


public void onClick(View view) {
    MyThread myThread = new MyThread();
}

public class MyThread extends Thread {
    // Конструктор
    MyThread() {
        // Создаём новый поток
        super("Второй поток");
        Log.i(TAG, "Создан второй поток " + this);
        start(); // Запускаем поток
    }

    public void run() {
        Log.d(TAG, "Mой поток запущен...");

        try {
            for (int i = 5; i > 0; i--) {
                Log.i(TAG, "Второй поток: " + i);
                Thread.sleep(500);
            }
        } catch (InterruptedException e) {
            Log.i(TAG, "Второй поток прерван");
        }
    }
}

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

Есть более сложный вариант создания потока. Для создания нового потока нужно реализовать интерфейс Runnable. Вы можете создать поток из любого объекта, реализующего интерфейс Runnable и объявить метод run().

Внутри метода run() вы размещаете код для нового потока. Этот поток завершится, когда метод вернёт управление.

Когда вы объявите новый класс с интерфейсом Runnable, вам нужно использовать конструктор:


Thread(Runnable объект_потока, String имя_потока)

В первом параметре указывается экземпляр класса, реализующего интерфейс. Он определяет, где начнётся выполнение потока. Во втором параметре передаётся имя потока.

После создания нового потока, его нужно запустить с помощью метода start(), который, по сути, выполняет вызов метода run().

Создадим новый поток внутри учебного проекта в виде вложенного класса и запустим его.


package ru.alexanderklimov.expresscourse;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import static ru.alexanderklimov.expresscourse.R.id.textViewInfo;

public class MainActivity extends AppCompatActivity {

    final String TAG = "ExpressCourse";

    private Button mButton;
    private EditText mResultEditText;
    private TextView mInfoTextView;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mButton = findViewById(R.id.buttonGetResult);
        mResultEditText = findViewById(R.id.editText);
        mInfoTextView = findViewById(textViewInfo);
    }

    public void onClick(View view) {
        new MyRunnable(); // создаём новый поток

        try {
            for (int i = 5; i > 0; i--) {
                Log.i(TAG, "Главный поток: " + i);
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            Log.i(TAG, "Главный поток прерван");
        }
    }

    class MyRunnable implements Runnable {
        Thread thread;

        // Конструктор
        MyRunnable() {
            // Создаём новый второй поток
            thread = new Thread(this, "Поток для примера");
            Log.i(TAG, "Создан второй поток " + thread);
            thread.start(); // Запускаем поток
        }

        // Обязательный метод для интерфейса Runnable
        public void run() {
            try {
                for (int i = 5; i > 0; i--) {
                    Log.i(TAG, "Второй поток: " + i);
                    Thread.sleep(500);
                }
            } catch (InterruptedException e) {
                Log.i(TAG, "Второй поток прерван");
            }
        }
    }
}

Внутри конструктора MyRunnable() мы создаём новый объект класса Thread

thread = new Thread(this, "Поток для примера");

В первом параметре использовался объект this, что означает желание вызвать метод run() этого объекта. Далее вызывается метод start(), в результате чего запускается выполнение потока, начиная с метода run(). В свою очередь метод запускает цикл для нашего потока. После вызова метода start(), конструктор MyRunnable() возвращает управление приложению. Когда главный поток продолжает свою работу, он входит в свой цикл. После этого оба потока выполняются параллельно.

Можно запускать несколько потоков, а не только второй поток в дополнение к первому. Это может привести к проблемам, когда два потока пытаюсь работать с одной переменной одновременно.

Ключевое слово syncronized - синхронизированные методы

Для решения проблемы с потоками, которые могут внести путаницу, используется синхронизация.

Метод может иметь модификатор syncronized. Когда поток находится внутри синхронизированного метода, все другие потоки, которые пытаются вызвать его в том же экземпляре, должны ожидать. Это позволяет исключить путаницу, когда несколько потоков пытаются вызвать метод.


syncronized void meow(String msg);

Кроме того, ключевое слово syncronized можно использовать в качестве оператора. Вы можете заключить в блок syncronized вызовы методов какого-нибудь класса:


syncronized(объект) {
    // операторы, требующие синхронизации
}

Looper

Поток имеет в своём составе сущности Looper, Handler, MessageQueue.

Каждый поток имеет один уникальный Looper и может иметь много Handler.

Считайте Looper вспомогательным объектом потока, который управляет им. Он обрабатывает входящие сообщения, а также даёт указание потоку завершиться в нужный момент.

Поток получает свой Looper и MessageQueue через метод Looper.prepare() после запуска. Looper.prepare() идентифицирует вызывающий потк, создаёт Looper и MessageQueue и связывает поток с ними в хранилище ThreadLocal. Метод Looper.loop() следует вызывать для запуска Looper. Завершить его работу можно через метод looper.quit().


class LooperThread extends Thread {
    public Handler mHandler;

    public void run() {
        Looper.prepare();

        mHandler = new Handler() {
            public void handleMessage(Message msg) {
                // process incoming messages here
            }
        };
        Looper.loop();
    }
}

Используйте статический метод getMainLooper() для доступа к Looper главного потока:


Looper mainLooper = Looper.getMainLooper();

Создадим два потока. Один запустим в основном потоке, а второй отдельно от основного. Нам будет достаточно двух кнопок и метки.


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:tools="http://schemas.android.com/tools"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical"
              tools:context=".MainActivity" >

    <Button
        android:id="@+id/button_start"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="start()" />
    <Button
        android:id="@+id/button_run"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="run()" />
    <TextView
        android:id="@+id/textview_info"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

Обратите внимание, как запускаются потоки. Первый поток запускается с помощью метода start(), а второй - run(). Затем проверяем, в каком потоке мы находимся.


package ru.alexanderklimov.as23;

import android.os.Bundle;
import android.os.Looper;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;


public class MainActivity extends AppCompatActivity {

    TextView mInfoTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button startButton = (Button) findViewById(R.id.button_start);
        Button runButton = (Button) findViewById(R.id.button_run);
        mInfoTextView = (TextView) findViewById(R.id.textview_info);

        startButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Thread thread = new Thread(new MyRunnable());
                thread.start(); //в фоновом потоке
            }
        });

        runButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Thread thread = new Thread(new MyRunnable());
                thread.run(); //в текущем потоке
            }
        });
    }

    private class MyRunnable implements Runnable {

        @Override
        public void run() {
            // Проверяем, в каком потоке находимся
            if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
                mInfoTextView.setText("В основном потоке");
            } else {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        mInfoTextView.setText("В фоновом потоке");
                    }
                });
            }
        }
    }
}

Эта тема достаточно сложная и для большинства не представляет интереса и необходимости изучать.

В Android потоки в чистом виде используются всё реже и реже, у системы есть собственные способы.

Реклама