Недостатки при использовании потоков
Далее мы рассмотрим, как создавать и использовать потоки. Это довольно легко. Однако при создании многопоточного приложения нам следует учитывать ряд обстоятельств, которые негативно могут сказаться на работе приложения.
На некоторых платформах запуск новых потоков может замедлить работу приложения. Что может иметь большое значение, если нам критичная производительность приложения.
Для каждого потока создается свой собственный стек в памяти, куда помещаются все локальные переменные и ряд других данных, связанных с выполнением потока. Соответственно, чем больше потоков создается, тем больше памяти используется. При этом надо помнить, в любой системе размеры используемой памяти ограничены. Кроме того, во многих системах может быть ограничение на количество потоков. Но даже если такого ограничения нет, то в любом случае имеется естественное ограничение в виде максимальной скорости процессора.
Для создания нового потока мы можем создать новый класс, либо наследуя его от класса Thread, либо реализуя в классе интерфейсRunnable.
Создадим свой класс на основе Thread:
public class JThread extends Thread {
JThread(String name){
super(name);
}
public void run(){
System.out.printf("Поток %s начал работу... \n", Thread.currentThread().getName());
try{
Thread.sleep(500);
}
catch(InterruptedException e){
System.out.println("Поток прерван");
}
System.out.printf("Поток %s завершил работу... \n", Thread.currentThread().getName());
}
}
Класс потока называется JThread. Предполагается, что в конструктор класса передается имя потока, которое затем передается в конструктор базового класса. И также здесь переопределяется метод run(), код которого собственно и будет представлять весь тот код, который выполняется в потоке.
Теперь применим этот класс в главном классе программы:
public static void main(String[] args) {
System.out.println("Главный поток начал работу...");
new JThread("JThread").start();
System.out.println("Главный поток завершил работу...");
}
Консольный вывод:
Главный поток начал работу...
Главный поток завершил работу...
Поток JThread начал работу...
Поток JThread завершил работу...
Здесь в методе main в конструктор JThread передается произвольное название потока, и затем вызывается метод start(). По сути этот метод как раз и вызывает переопределенный метод run() класса JThread.
Обратите внимание, что главный поток завершает работу раньше, чем порожденный им дочерний поток JThread.
Аналогично созданию одного потока мы можем запускать сразу несколько потоков:
public static void main(String[] args) {
System.out.println("Главный поток начал работу...");
for(int i=1; i<6;i++)
new JThread("JThread " + i).start();
System.out.println("Главный поток завершил работу...");
}
Консольный вывод:
Главный поток начал работу...
Главный поток завершил работу...
Поток JThread 2 начал работу...
Поток JThread 5 начал работу...
Поток JThread 4 начал работу...
Поток JThread 1 начал работу...
Поток JThread 3 начал работу...
Поток JThread 1 завершил работу...
Поток JThread 2 завершил работу...
Поток JThread 5 завершил работу...
Поток JThread 4 завершил работу...
Поток JThread 3 завершил работу...
При запуске потоков в примерах выше главный поток завершался до дочернего потока. Как правило, более распространенной ситуацией является случай, когда главный поток завершается самым последним. Для этого надо применить метод join():
public static void main(String[] args) {
System.out.println("Главный поток начал работу...");
JThread t= new JThread("JThread ");
t.start();
try{
t.join();
}
catch(InterruptedException e){
System.out.printf("Поток %s прерван", t.getName());
}
System.out.println("Главный поток завершил работу...");
}
Метод join() заставляет вызвавший поток (в данном случае главный поток) ожидать завершения вызываемого потока, для которого и применяется метод join (в данном случае поток JThread).
Консольный вывод:
Главный поток начал работу...
Поток JThread начал работу...
Поток JThread завершил работу...
Главный поток завершил работу...
Если в программе используется несколько дочерних потоков, и надо, чтобы главный поток завершался после дочерних, то для каждого дочернего потока надо вызвать метод join.
Реализация интерфейса Runnable
Другой способ определения потока представляет реализация интерфейса Runnable. Этот интерфейс имеет один метод run:
interface Runnable{
void run();
}
В методе run() собственно определяется весь тот код, который выполняется при запуске потока.
После определения объекта Runnable он передается в один из конструкторов класса Thread:
Thread(Runnable runnable, String threadName)
Для реализации интерфейса определим следующий класс MyThread:
public class MyThread implements Runnable {
MyThread(){
}
public void run(){
System.out.printf("Поток %s начал работу... \n", Thread.currentThread().getName());
try{
Thread.sleep(500);
}
catch(InterruptedException e){
System.out.println("Поток прерван");
}
System.out.printf("Поток %s завершил работу... \n", Thread.currentThread().getName());
}
}
Реализация интерфейса Runnable во многом аналогична переопределению класса Thread. Также в методе run определяется простейший код, который усыпляет поток на 500 миллисекунд.
Теперь используем этот класс в главном классе программы:
public static void main(String[] args) {
System.out.println("Главный поток начал работу...");
new Thread(new MyThread(),"MyThread").start();
System.out.println("Главный поток завершил работу...");
}
В методе main вызывается конструктор Thread, в который передается объект MyThread. И чтобы запустить поток, вызывается методstart(). В итоге консоль выведет что-то наподобие следующего:
Главный поток начал работу...
Главный поток завершил работу...
Поток MyThread начал работу...
Поток MyThread завершил работу...
Завершение потока
Особо следует остановиться на механизме завершения потока. Все примеры выше представляли поток как последовательный набор операций. После выполнения последней операции завершался и поток. Однако нередко имеет место и другая организация потока в виде бесконечного цикла. Например, поток сервера в бесконечном цикле прослушивает определенный порт на предмет получения данных. И в этом случае мы также должны предусмотреть механизм завершения потока. Как правило, это делается с помощью опроса логической переменной. И если она равна, например, false, то поток завершает бесконечный цикл и заканчивает свое выполнение.
Определим следующий класс потока:
public class MyThread implements Runnable {
private boolean isActive;
void disable(){
isActive=false;
}
MyThread(){
isActive = true;
}
public void run(){
System.out.printf("Поток %s начал работу... \n", Thread.currentThread().getName());
int counter=1; // счетчик циклов
while(isActive){
System.out.println("Цикл " + counter++);
try{
Thread.sleep(500);
}
catch(InterruptedException e){
System.out.println("Поток прерван");
}
}
System.out.printf("Поток %s завершил работу... \n", Thread.currentThread().getName());
}
}
Переменная isActive указывает на активность потока. С помощью метода disable() мы можем сбросить состояние этой переменной.
Теперь используем этот класс:
public static void main(String[] args) {
System.out.println("Главный поток начал работу...");
MyThread myThread = new MyThread();
new Thread(myThread,"MyThread").start();
try{
Thread.sleep(1100);
myThread.disable();
Thread.sleep(1000);
}
catch(InterruptedException e){
System.out.println("Поток прерван");
}
System.out.println("Главный поток завершил работу...");
}
Итак, вначале запускается дочерний поток: new Thread(myThread,"MyThread").start(). Затем на 1100 миллисекунд останавливаем главный поток и потом вызываем метод myThread.disable(), который переключает в потоке флаг isActive. И дочерний поток завершается.
Дата добавления: 2017-01-26; просмотров: 1049;