Как получить объект из потока java
Перейти к содержимому

Как получить объект из потока java

  • автор:

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

В русской терминологии за термином 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)

Как вариант, перенести вызов метода 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(); 

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

Обратите внимание, как запускаются потоки. Первый поток запускается с помощью метода 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 потоки в чистом виде используются всё реже и реже, у системы есть собственные способы.

Получить объект класс из потока в Java

Сервер приложения IBM WebSphere, соответственно, запущен на IBM JVM. Приложение работает в несколько потоков, как WebContainer, так и фоновые процессы. В отдельным потоке получаю все потоки, которые запущены в данной JVM, таким образом:

Set threadSet = Thread.getAllStackTraces().keySet(); for(Thread t: threadSet)

Можно ли каким-то образом получить список всех объектов, например класса java.sql.Connection, или его наследников, и уже от их имени выполнить какой-то запрос к БД. Например, несколько потоков имею свои подключения к БД и мне надо знать какой SQL_ID используется каждым потоком. Возможно это реализовать? Может есть другой способ решения данной задачи?

Как получить объект из потока java

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

Наследование от класса Thread

Создадим свой класс на основе Thread:

class JThread extends Thread < JThread(String name)< super(name); >public void run() < System.out.printf("%s started. \n", Thread.currentThread().getName()); try< Thread.sleep(500); >catch(InterruptedException e) < System.out.println("Thread has been interrupted"); >System.out.printf("%s fiished. \n", Thread.currentThread().getName()); > > public class Program < public static void main(String[] args) < System.out.println("Main thread started. "); new JThread("JThread").start(); System.out.println("Main thread finished. "); >>

Класс потока называется JThread. Предполагается, что в конструктор класса передается имя потока, которое затем передается в конструктор базового класса. В конструктор своего класса потока мы можем передать различные данные, но главное, чтобы в нем вызывался конструктор базового класса Thread, в который передается имя потока.

И также в JThread переопределяется метод run() , код которого собственно и будет представлять весь тот код, который выполняется в потоке.

В методе main для запуска потока JThread у него вызывается метод start() , после чего начинается выполнение того кода, который определен в методе run:

new JThread("JThread").start();
Main thread started. Main thread finished. JThread started. JThread finished.

Здесь в методе main в конструктор JThread передается произвольное название потока, и затем вызывается метод start() . По сути этот метод как раз и вызывает переопределенный метод run() класса JThread.

Обратите внимание, что главный поток завершает работу раньше, чем порожденный им дочерний поток JThread.

Аналогично созданию одного потока мы можем запускать сразу несколько потоков:

public static void main(String[] args)

Main thread started. Main thread finished. JThread 2 started. JThread 5 started. JThread 4 started. JThread 1 started. JThread 3 started. JThread 1 finished. JThread 2 finished. JThread 5 finished. JThread 4 finished. JThread 3 finished.

Ожидание завершения потока

При запуске потоков в примерах выше Main thread завершался до дочернего потока. Как правило, более распространенной ситуацией является случай, когда Main thread завершается самым последним. Для этого надо применить метод join() . В этом случае текущий поток будет ожидать завершения потока, для которого вызван метод join:

public static void main(String[] args) < System.out.println("Main thread started. "); JThread t= new JThread("JThread "); t.start(); try< t.join(); >catch(InterruptedException e) < System.out.printf("%s has been interrupted", t.getName()); >System.out.println("Main thread finished. "); >

Метод join() заставляет вызвавший поток (в данном случае Main thread) ожидать завершения вызываемого потока, для которого и применяется метод join (в данном случае JThread).

Main thread started. JThread started. JThread finished. Main thread finished.

Если в программе используется несколько дочерних потоков, и надо, чтобы Main thread завершался после дочерних, то для каждого дочернего потока надо вызвать метод join.

Реализация интерфейса Runnable

Другой способ определения потока представляет реализация интерфейса Runnable . Этот интерфейс имеет один метод run :

interface Runnable

В методе run() собственно определяется весь тот код, который выполняется при запуске потока.

После определения объекта Runnable он передается в один из конструкторов класса Thread:

Thread(Runnable runnable, String threadName)

Для реализации интерфейса определим следующий класс MyThread:

class MyThread implements Runnable < public void run()< System.out.printf("%s started. \n", Thread.currentThread().getName()); try< Thread.sleep(500); >catch(InterruptedException e) < System.out.println("Thread has been interrupted"); >System.out.printf("%s finished. \n", Thread.currentThread().getName()); > > public class Program < public static void main(String[] args) < System.out.println("Main thread started. "); Thread myThread = new Thread(new MyThread(),"MyThread"); myThread.start(); System.out.println("Main thread finished. "); >>

Реализация интерфейса Runnable во многом аналогична переопределению класса Thread. Также в методе run определяется простейший код, который усыпляет поток на 500 миллисекунд.

В методе main вызывается конструктор Thread, в который передается объект MyThread. И чтобы запустить поток, вызывается метод start() . В итоге консоль выведет что-то наподобие следующего:

Main thread started. Main thread finished. MyThread started. MyThread finished.

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

public class Program < public static void main(String[] args) < System.out.println("Main thread started. "); Runnable r = ()-> < System.out.printf("%s started. \n", Thread.currentThread().getName()); try< Thread.sleep(500); >catch(InterruptedException e) < System.out.println("Thread has been interrupted"); >System.out.printf("%s finished. \n", Thread.currentThread().getName()); >; Thread myThread = new Thread(r,"MyThread"); myThread.start(); System.out.println("Main thread finished. "); > >

Потоки в Java: От рождения до смерти. Введение в многопоточность

Всем привет! В этой статье я бы хотел погрузить в мир жизненного цикла потоков начинающих программистов на Java, заранее извиняюсь за злоупотребление слова «поток» в этой статье, но я надеюсь так будет даже лучше для понимания. Поехали!

Жизненный цикл потока — основная концепция Java, которую мы подробно рассмотрим в этой статье. Мы будем использовать краткую иллюстрированную диаграмму и фрагменты практического кода, чтобы более глубоко понять состояния потока во время его выполнения. Эта статья о создании потока — отличное начало для понимания потоков в Java.

Многопоточность в Java

Многопоточность в языке Java определяется основной концепцией потока. Потоки проходят через различные состояния в течение своего жизненного цикла:

Состояния потока в Java

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

В Java класс java.lang.Thread является фундаментальным компонентом, который позволяет выполнять параллельное программирование. Этот класс предоставляет множество функций для управления выполнением нескольких потоков в программе.

Класс java.lang.Thread содержит перечисление статических состояний, в которых может находиться поток. Эти состояния играют решающую роль в понимании жизненного цикла потока.

Первое состояние — «NEW «. Это состояние представляет собой недавно созданный поток, который еще не начал свое выполнение. В этом состоянии поток готов к планированию операционной системой, но ему не назначено никакого процессорного времени.

Следующее состояние — «RUNNABLE«. Поток в этом состоянии либо запущен, либо готов к выполнению. Однако он может ожидать выделения ресурсов, таких как процессорное время или доступ к общему ресурсу. Как только необходимые ресурсы будут доступны, поток может продолжить свое выполнение.

Другое состояние «BLOCKED «. В этом состоянии поток ожидает получения блокировки монитора(monitor lock), которая необходима для ввода или повторного ввода синхронизированного блока или метода. Поток остается в этом состоянии до тех пор, пока monitor lock не станет доступен.

Ещё одно состояние — «WAITING«. Когда поток находится в этом состоянии, он ожидает, пока какой-либо другой поток выполнит определенное действие без каких-либо временных ограничений. Это может произойти, например, когда один поток ожидает сигнала от другого для продолжения своего выполнения.

Понимая различные состояния потока, разработчики могут эффективно управлять выполнением своих многопоточных приложений. Это позволяет им оптимизировать использование ресурсов и эффективно обрабатывать синхронизацию, обеспечивая бесперебойную и результативную работу своих программ.
Чтобы полностью понять различные состояния потока, важно глубже вникнуть в каждое из них. Давайте начнем с состояния «TIMED_WAITING», он похож на «WAITING», но есть некоторые отличия. Это состояние возникает, когда один поток ожидает от другого выполнения определенного действия, но только в течение определенного периода времени.

«TERMINATED» — состояние, когда поток завершил свое выполнение. Это означает, что он завершил назначенную ему задачу и больше не активен. Важно отметить, что как только поток достигает этого состояния, его нельзя перезапустить или возобновить работу.

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

Перейдём к примерам

NEW — это состояние, когда поток был создан, но еще не запущен. В этом состоянии поток ожидает своего запуска с помощью метода start().

Runnable runnable = new NewState(); Thread t = new Thread(runnable); System.out.println(t.getState());

В данном случае метод t.getState() в консоль выведет «NEW«

В многопоточной среде планировщик потоков(Thread-Scheduler) (который является частью JVM) выделяет фиксированное количество времени для каждого потока. Таким образом, он выполняется в течение определенного периода времени, затем передает управление другим выполняемым потокам.

Когда мы создаем новый поток и вызываем для него метод start(), он переходит из состояния NEW в состояние RUNNABLE. Потоки в этом состоянии либо запущены, либо готовы к запуску, но они ожидают выделения ресурсов системой.

Например, давайте добавим метод t.start() в наш предыдущий код и попытаемся получить доступ к его текущему состоянию:

Runnable runnable = new NewState(); Thread t = new Thread(runnable); t.start(); System.out.println(t.getState());

Теперь метод t.getState() вероятнее всего в консоль выведет «RUNNABLE«.
Почему вероятнее всего? Дело в том, что когда наш элемент управления достигнет t.getState(), мы не всегда можем быть уверены, что он будет находиться в состоянии RUNNABLE. Это связано с тем, что в некоторых случаях элемент может быть немедленно запланирован планировщиком потоков (Thread-Scheduler) и завершить своё выполнение. Именно в таких ситуациях возможны другие результаты.

Как мы уже выяснили, поток переходит в состояние Blocked, когда ожидает блокировки монитора(monitor lock) и пытается получить доступ к разделу кода, который заблокирован каким-либо другим потоком.

Давайте попробуем воспроизвести это состояние:

public class BlockedState < public static void main(String[] args) throws InterruptedException < Thread t1 = new Thread(new DemoBlockedRunnable()); Thread t2 = new Thread(new DemoBlockedRunnable()); t1.start(); t2.start(); Thread.sleep(1000); System.out.println(t2.getState()); System.exit(0); >> class DemoBlockedRunnable implements Runnable < @Override public void run() < commonResource(); >public static synchronized void commonResource() < while(true) < >> >
  1. Мы создали два разных потока – t1 и t2
  2. t1 запускается и вводит синхронизированный метод commonResource(); это означает, что только один поток может получить к нему доступ; все остальные последующие потоки, которые попытаются получить доступ к этому методу, будут заблокированы от дальнейшего выполнения до тех пор, пока текущий не завершит обработку.
  3. Когда t1 входит в этот метод, он сохраняется в бесконечном цикле while; Это сделано для имитации интенсивной обработки, чтобы все остальные потоки не могли войти в этот метод.
  4. Теперь, когда мы запускаем t2, он пытается ввести метод commonResource(), к которому уже обращается t1, таким образом, t2 будет сохранен в состоянии BLOCKED.

Поток находится в состоянии WAITING, когда он ожидает, пока какой-либо другой поток выполнит определенное действие. Согласно JavaDocs, любой поток может войти в это состояние, вызвав любой из этих трех методов:

1.object.wait()

2.thread.join()

3.LockSupport.park()

Обратите внимание, что в wait() и join() – мы не определяем какой-либо период ожидания, поскольку этот сценарий рассматривается в следующем разделе.

А пока давайте попробуем воспроизвести это состояние:

public class WaitingState implements Runnable < public static Thread t1; public static void main(String[] args) < t1 = new Thread(new WaitingState()); t1.start(); >public void run() < Thread t2 = new Thread(new DemoWaitingStateRunnable()); t2.start(); try < t2.join(); >catch (InterruptedException e) < Thread.currentThread().interrupt(); e.printStackTrace(); >> > class DemoWaitingStateRunnable implements Runnable < public void run() < try < Thread.sleep(1000); >catch (InterruptedException e) < Thread.currentThread().interrupt(); e.printStackTrace(); >System.out.println(WaitingState.t1.getState()); > >
  1. Мы создали и запустили t1
  2. t1 создает t2 и запускает его
  3. Пока продолжается работа t2, мы вызываем t2.join(), это переводит t1 в состояние ожидания(WAITING), пока t2 не завершит выполнение.
  4. Поскольку t1 ожидает завершения t2, мы вызываем t1.getState() из t2 и получаем результат «WAITING«

Поток находится в состоянии TIMED_WAITING, когда он ожидает, пока другой поток выполнит определенное действие в течение заданного промежутка времени.

Согласно JavaDocs, существует пять способов перевести поток в состояние TIMED_WAITING:

Давайте попробуем воспроизвести это состояние:

public class TimedWaitingState < public static void main(String[] args) throws InterruptedException < DemoTimeWaitingRunnable runnable= new DemoTimeWaitingRunnable(); Thread t1 = new Thread(runnable); t1.start(); Thread.sleep(1000); System.out.println(t1.getState()); >> class DemoTimeWaitingRunnable implements Runnable < @Override public void run() < try < Thread.sleep(5000); >catch (InterruptedException e) < Thread.currentThread().interrupt(); e.printStackTrace(); >> >

Здесь мы создали и запустили поток t1, который переводится в спящее состояние с периодом ожидания 5 секунд; результатом будет «TIMED_WAITING»

TERMINATED — это состояние мертвого потока. Поток находится в состоянии TERMINATED, когда он либо завершил выполнение, либо был как-то был прерван.

Попробуем вызвать это состояние:

public class TerminatedState implements Runnable < public static void main(String[] args) throws InterruptedException < Thread t1 = new Thread(new TerminatedState()); t1.start(); Thread.sleep(1000); System.out.println(t1.getState()); >@Override public void run() < >>

Здесь, мы запустили поток t1, но метод Thread.sleep(1000) дает время, для завершения t1, вследствие чего, эта программа выдает нам в результате «TERMINATED».

Вывод

В этой статье мы узнали о жизненном цикле потока в Java. Мы рассмотрели все шесть состояний, определенных Thread и воспроизвели их с помощью кратких примеров.

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

  • java
  • потоки
  • многопоточность
  • многопоточное программирование
  • Программирование
  • Java
  • Параллельное программирование

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *