Приветствую Вас ГостьВторник, 14.05.2024, 05:49

Программирование на Java, Android, Delphi


Блог

Главная » 2013 » Февраль » 4 » Многопоточное программирование в Java
14:03
Многопоточное программирование в Java
Java поддерживает многопоточное программирование. Создавать и запускать потоки в Java можно двумя путями:
  • реализовать в классе интерфейс Runnable
  • наследовать свой класс от класса Thread
Самый гибкий способ — реализовать в своем классе интерфейс Runnable. По сути мы любой класс можем превратить в поток. Для этого достаточно написать в нем реализацию единственного метода run(). Или если нет возможности менять исходный код класса, то можем реализовать подкласс с реализацией Runnable для нужного класса. После того, как класс реализован, нужно создать поток и передать ему ссылку на интерфейс Runnable, после чего запустить поток методом start(). В примере 2 случая: когда объект типа Thread содержится в классе, реализующий интерфейс Runnable и когда он описан и создан вне его:
class FirstTheard implements Runnable{
Thread th;

//в констукторе создаем и запускаем поток
FirstTheard(){
th = new Thread(this);
th.start();
}

@Override
public void run() {

//в цикле выводим сообщения
for(int i = 0; i < 10; i ++){
JOptionPane.showMessageDialog(null, "FirstTheard " + i);
try {
//прекращаем выполнение потока на время
Thread.sleep(500);
} catch (InterruptedException ex) {
Logger.getLogger(FirstTheard.class.getName()).log(Level.SEVERE, null, ex);
}
}
}

}

class SecondTheard implements Runnable{

Thread th;

//в констукторе создаем и запускаем поток
SecondTheard(){
th = new Thread(this);
th.start();
}

@Override
public void run() {
//в цикле выводим сообщения
for(int i = 0; i < 10; i ++){
try {
JOptionPane.showMessageDialog(null, "SecondTheard " + i);
//прекращаем выполнение потока на время
Thread.sleep(500);
} catch (InterruptedException ex) {
Logger.getLogger(SecondTheard.class.getName()).log(Level.SEVERE, null, ex);
}
}
}

}


//в этом классе только реализация Runnable, создание и запуск потока происходит вне его
class ThirdTheard implements Runnable{

@Override
public void run() {
//в цикле выводим сообщения
for(int i = 0; i < 10; i ++){
try {
JOptionPane.showMessageDialog(null, "ThirdTheard " + i);
//прекращаем выполнение потока на время
Thread.sleep(500);
} catch (InterruptedException ex) {
Logger.getLogger(SecondTheard.class.getName()).log(Level.SEVERE, null, ex);
}
}
}

}



private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
//создание и запуск певого потока
new FirstTheard();
//создание и запуск второго потока
new SecondTheard();

//создание третьего потока
Thread t = new Thread(new ThirdTheard());
//запуск третьего потока
t.start();

}
Второй способ создать поток — создать подкласс класса Thread и реализовать метод run(). При этом для запуска потока нужно вызвать метод start() данного подкласса.
class FirstThread extends Thread{

//перекрываем метод run() - код потока
@Override
public void run(){
//в цикле выводим сообщения
for(int i = 0; i < 10; i ++){
try {
JOptionPane.showMessageDialog(null, "FirstThread " + i);
//прекращаем выполнение потока на время
Thread.sleep(500);
} catch (InterruptedException ex) {
Logger.getLogger(FirstThread.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}



class SecondThread extends Thread{

//в конструкторе запускаем поток
SecondThread(){
this.start();
}

//перекрываем метод run() - код потока
@Override
public void run(){
//в цикле выводим сообщения
for(int i = 0; i < 10; i ++){
try {
JOptionPane.showMessageDialog(null, "SecondThread " + i);
//прекращаем выполнение потока на время
Thread.sleep(500);
} catch (InterruptedException ex) {
Logger.getLogger(SecondThread.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}


private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
//создание и запуск певого потока
FirstThread ft = new FirstThread();
ft.start();

//создание и запуск второго потока (более компактная запись)
//здесь запуск потока вынесен в конструктор класса
new SecondThread();

}
Для проверки активности потока используется метод isAlive(), для того, чтобы один поток ожидал другой используется метод join(). Тот поток, который вызвал будет ждать завершение потока чей метод join() он вызвал.
class MyThread implements Runnable{

JTextArea ta;
String name;

MyThread(JTextArea ta, String name){
//передаем ссылку на текстовую область для вывода
this.ta = ta;
this.name = name;
}

@Override public void run(){
Random rnd = new Random();
for(int i = 0; i < 50; i ++){
//ta.setText(ta.getText() + name + " " + i + "\n");
ta.append(name + " " + i + "\n");

try {
Thread.sleep(rnd.nextInt(100));
} catch (InterruptedException ex) {
JOptionPane.showMessageDialog(null, "Произошла ошибка в потоке " + name);
}
}
}
}



private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
//создаем объекты - потоки
Thread th1 = new Thread(new MyThread(jTextArea1, "Северный поток"));
Thread th2 = new Thread(new MyThread(jTextArea1, "Южный поток"));
Thread th3 = new Thread(new MyThread(jTextArea1, "Западный поток"));
Thread th4 = new Thread(new MyThread(jTextArea1, "Восточный поток"));

//запускаем потоки
th1.start();
th2.start();
th3.start();
th4.start();

//проверяем потоки на активность методом isAlive()
jTextArea1.append("Северный поток запущен: " + Boolean.toString(th1.isAlive()) + "\n");
jTextArea1.append("Южный поток запущен: " + Boolean.toString(th2.isAlive()) + "\n");
jTextArea1.append("Западный поток запущен: " + Boolean.toString(th3.isAlive()) + "\n");
jTextArea1.append("Восточный поток запущен: " + Boolean.toString(th4.isAlive()) + "\n");

try {
//заставляем основной поток ждать все четыре потока
th1.join();
th2.join();
th3.join();
th4.join();
} catch (InterruptedException ex) {
JOptionPane.showMessageDialog(null, "Произошла ошибка в потоке ");
}

//проверяем потоки на активность методом isAlive()
jTextArea1.append("Северный поток запущен: " + Boolean.toString(th1.isAlive()) + "\n");
jTextArea1.append("Южный поток запущен: " + Boolean.toString(th2.isAlive()) + "\n");
jTextArea1.append("Западный поток запущен: " + Boolean.toString(th3.isAlive()) + "\n");
jTextArea1.append("Восточный поток запущен: " + Boolean.toString(th4.isAlive()) + "\n");

}
Для установки приоритета потока используется метод setPrority(int уровень). Для установки уровня приоритета можно использовать константы, объявленные в Thread. Пример:
 private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) { 
//создаем объекты - потоки
Thread th1 = new Thread(new MyThread(jTextArea1, "Северный поток"));
Thread th2 = new Thread(new MyThread(jTextArea1, "Южный поток"));
Thread th3 = new Thread(new MyThread(jTextArea1, "Западный поток"));
Thread th4 = new Thread(new MyThread(jTextArea1, "Восточный поток"));

//установим приоритеты потоков. Как мы увидим th4 и th1 будут более приоритетными в выводе чем th2 и th3
th4.setPriority(Thread.MAX_PRIORITY);
th3.setPriority(Thread.MIN_PRIORITY);
th2.setPriority(Thread.MIN_PRIORITY);
th1.setPriority(Thread.NORM_PRIORITY);

//запускаем потоки
th1.start();
th2.start();
th3.start();
th4.start();

}
Если несколько потоков будут работать с одним объектом (вызывать методы), то потребуется синхронизация потоков. Каждый класс Java имеет ассоциированный с ним монитор, поэтому метод объявленный с ключевым словом synchronized становится синхронизированным и если один поток вызовет этот метод, то остальные потоки не смогут вызывать ни один синхронизированный метод данного объекта, пока первый поток не выйдет из данного метода.
//этот класс будет совместно использоваться различными потоками
class ShowString{
JTextArea ta;

ShowString(JTextArea ta) {
this.ta = ta;
}

//этот метод будет или синхронизированным или нет. В разных случаях получим разный вывод.
synchronized void Show(String str){
Random rnd = new Random();

for(int i = 0 ; i < 100; i ++){
ta.append(str + "\n");

//приостанавливаем поток
try{
Thread.sleep(rnd.nextInt(10));
}
catch(InterruptedException e){
JOptionPane.showMessageDialog(null, "Ошибка");
}

}
}
}

//класс потока реализует интерфейс Runnable
class TestThread implements Runnable{
JTextArea ta;
String value;
ShowString ss;

Thread th;

//в конструкторе устанавливаем ссылки на объекты и значения переменных
TestThread(ShowString ss, JTextArea ta, String value){
this.ss = ss;
this.ta = ta;
this.value = value;

//создаем объект потока в конструкторе и запускаем его
th = new Thread(this);
th.start();
}

@Override
public void run(){
//в потоке вызываем метод (он или синхронизирован или нет - получим различный вывод)
ss.Show(value);
}
}



private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
//создаем объект, который совместно используют несколько потоков
ShowString ss = new ShowString(jTextArea1);

new TestThread(ss, jTextArea1, "Первый поток");
new TestThread(ss, jTextArea1, "Второй поток");
new TestThread(ss, jTextArea1, "Третий поток");

}
Если нам нужно синхронизировать доступ к объектам класса, методы которого не синхронизированы и мы не можем изменять код класса, то синхронизация все-таки возможна за счет использования следующего оператора:
 synchronized(объект){
//операторы которые нужно синхронизировать
}
Предыдущий пример будет выглядеть так:
//этот класс будет совместно использоваться различными потоками
class ShowString{
JTextArea ta;

ShowString(JTextArea ta) {
this.ta = ta;
}

//этот метод будет синхронизирован оператором synchronized(объект)
void Show(String str){
Random rnd = new Random();

for(int i = 0 ; i < 100; i ++){
ta.append(str + "\n");

//приостанавливаем поток
try{
Thread.sleep(rnd.nextInt(10));
}
catch(InterruptedException e){
JOptionPane.showMessageDialog(null, "Ошибка");
}

}
}
}

//класс потока реализует интерфейс Runnable
class TestThread implements Runnable{
JTextArea ta;
String value;
ShowString ss;

Thread th;

//в конструкторе устанавливаем ссылки на объекты и значения переменных
TestThread(ShowString ss, JTextArea ta, String value){
this.ss = ss;
this.ta = ta;
this.value = value;

//создаем объект потока в конструкторе и запускаем его
th = new Thread(this);
th.start();
}

@Override
public void run(){
//синхронизированный блок
synchronized(ss){
ss.Show(value);
}
}
}
Как мы выяснили, с объектом Java ассоциирован монитор, который позволяет выполнять synchronized методы объекта только одним потоком. Второй поток получит доступ к любому synchronized методу только после того, как текущий поток завершит метод. Мы можем управлять монитором объекта — например заставить прекратить выполнение метода потоком и отпустить монитор для другого потока. Второй поток может выполнить какой-нибудь метод также не полностью и освободить монитор для третьего потока или закончив выполнение метода запустить первый приостановленный поток. Для этого используются методы, объявленные в классе Object и доступные для любых объектов:
  • Метод wait() принуждает вызывающий поток отдать монитор и приостановить выполнение до тех пор пока другой поток не войдет в этот монитор и не вызовет метод notify() или notifyAll()
  • Метод notify() возобновляет выполнение потока, который вызвал wait() в том же объекте
  • Метод notifyAll() возобновляет работу всех потоков, которые вызвали wait() в том же объекте
Все эти методы могут быть вызваны только из синхронизированного контекста. Например есть очередь, которая содержит 1 элемент. Поток-источник будет наполнять эту очередь, а поток приемник будет выбирать данные:
class Queue{
//значение для хранения очереди (из 1 элемента)
private int value;
//переменная флаг есть или нет элемент в очереди
private boolean empty = true;
//объект для вывода текста
private JTextArea ta;

public Queue(JTextArea ta) {
this.ta = ta;
}



//положить элемент в очередь
synchronized void put(int value){

//пока в очереди есть значение ожидаем поток, который извлечет значение
while(!empty){
try {
wait();
} catch (InterruptedException ex) {
JOptionPane.showMessageDialog(null, "Ошибка потока");
}
}


empty = false;
this.value = value;
ta.append("Положили: " + Integer.toString(value) + "\n");

//уведомляем поток что положили значение
notify();
}
//извлечь элемент из очереди
synchronized int get(){

//пока в очереди нет значение ожидаем поток, который положит значение
while(empty){
try {
wait();
} catch (InterruptedException ex) {
JOptionPane.showMessageDialog(null, "Ошибка потока");
}
}

empty = true;
ta.append("Достали: " + Integer.toString(value) + "\n");
notify();
return value;
}


}



class Provider implements Runnable{
//числогенератор
private int id = 0;
//объект совместно разделяемой очереди
private Queue q;
//объект потока
private Thread th;

public Provider(Queue q) {
this.q = q;

//создаем объект потока и запускаем его
th = new Thread(this);
th.start();
}



@Override public void run(){
//код потока поставщика
while(true){
id = id + 1;
q.put(id);
}
}
}

class Reciver implements Runnable{
//объект совместно разделяемой очереди
private Queue q;
//объект потока
private Thread th;

public Reciver(Queue q) {
this.q = q;

//создаем объект потока и запускаем его
th = new Thread(this);
th.start();
}

@Override public void run(){
//код потока получателя
while(true){
q.get();
}
}
}


private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
//объект очереди, который используют потоки
Queue q = new Queue(jTextArea1);
//поток, который заполняет очередь
new Provider(q);
//поток, который извлекает из очереди
new Reciver(q);
}
Как рекомендует Oracle метод wait() должен вызываться в цикле, проверяющий условие, по которому поток ожидает. В противном случае могут возникнуть проблемы. Но вероятность их очень мала. Вот эти строчки из примера выше:
 //пока в очереди есть значение ожидаем поток, который извлечет значение
while(!empty){
try {
wait();
} catch (InterruptedException ex) {
JOptionPane.showMessageDialog(null, "Ошибка потока");
}
}
Хотя можно написать и так без цикла:
 if(!empty){
try {
wait();
} catch (InterruptedException ex) {
JOptionPane.showMessageDialog(null, "Ошибка потока");
}
}
В прошлом примере в очередь могли помещать только 1 элемент, расширим этот пример — сделаем очередь из 10 элементов. Код получился такой:
class Queue{
//значение для хранения очереди (из 10 элементов)
private int[] value = new int[10];
//переменная флаг заполнена или нет очередь
private boolean empty = true;
//указатель на текущий элемент очереди
private int ptr = 0;
//объект для вывода текста
private JTextArea ta;

public Queue(JTextArea ta) {
this.ta = ta;
}



//положить элемент в очередь
synchronized void put(int value){

//если очередь заполнена - приостанавливаем поток
while(!empty){
try {
wait();
} catch (InterruptedException ex) {
JOptionPane.showMessageDialog(null, "Ошибка потока");
}
}

//если есть место в очереди - кладем и выводим сообщение
if(ptr < this.value.length){
this.value[ptr ++] = value;
ta.append("Положили: " + Integer.toString(value) + "\n");
}
//иначе устанавливаем флаги и указатели
else{
//устанавливаем что массив полный
empty = false;
//указатель на начало очереди
ptr = 0;
//уведомляем другой поток, что заполнили очередь
notify();

//так как пришел элемент для постановки в очередь, мы не можем его потерять
//останавливаем поток и ждем, чтобы положить элемент в очередь
while(!empty){
try {
wait();
} catch (InterruptedException ex) {
JOptionPane.showMessageDialog(null, "Ошибка потока");
}
}
//кладем его в очередь
if(ptr < this.value.length){
this.value[ptr ++] = value;
ta.append("Положили: " + Integer.toString(value) + "\n");
}


}//end else

}
//извлечь элемент из очереди
synchronized int get(){

//пока очередь пустая - отдаем монитор, пусть другой поток наполнит очередь
while(empty){
try {
wait();
} catch (InterruptedException ex) {
JOptionPane.showMessageDialog(null, "Ошибка потока");
}
}

//извлекаем значение из очереди
if(ptr < value.length){
int result = value[ptr];
ptr ++;
ta.append("Достали: " + Integer.toString(result) + "\n");
return result;
}
//иначе устанавливаем флаги и указатель на начало очереди и уведомляем
//другой поток, чтобы он начал заполнять очередь
else{
empty = true;
ptr = 0;
notify();
return 0;
}
}


}



class Provider implements Runnable{
//числогенератор
private int id = 0;
//объект совместно разделяемой очереди
private Queue q;
//объект потока
private Thread th;

public Provider(Queue q) {
this.q = q;

//создаем объект потока и запускаем его
th = new Thread(this);
th.start();
}



@Override public void run(){
//код потока поставщика
while(true){
id = id + 1;
q.put(id);
}
}
}

class Reciver implements Runnable{
//объект совместно разделяемой очереди
private Queue q;
//объект потока
private Thread th;

public Reciver(Queue q) {
this.q = q;

//создаем объект потока и запускаем его
th = new Thread(this);
th.start();
}

@Override public void run(){
//код потока получателя
while(true){
q.get();
}
}
}


private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
//объект очереди, который используют потоки
Queue q = new Queue(jTextArea1);
//поток, который заполняет очередь
new Provider(q);
//поток, который извлекает из очереди
new Reciver(q);
}
Поток может быть остановлен, запущен снова и прерван. Раньше для этого использовались методы suspend(), resume(), stop(). Сейчас эти методы объявлены как нежелательные и использовать их нельзя. Сейчас приостанавливать, запускать и завершать поток нужно логикой в коде метода run() используя методы wait(), notify(). Вот пример:
class TestThread implements Runnable{

private boolean suspendFlag = false;
private JTextArea ta;
private int i = 0;

//конструктор
TestThread(JTextArea ta){
this.ta = ta;
}

@Override
public void run() {
while(true){
ta.append(Integer.toString(i ++) + "\n");

//код проверки на останов потока в синхронизированном контексте
synchronized(this){
//если suspendFlag установлен - прекращаем действие потока
while(suspendFlag) {try {
wait();
} catch (InterruptedException ex) {
ta.append("Произошла ошибка в потоке\n");
}
}
}
}
}

synchronized void suspendThread(){
//для остановки потока достаточно взвести флаг - в коде потока он проверится и вызовется метод wait()
suspendFlag = true;
ta.append("Остановка потока\n");
}
synchronized void resumeThread(){
//сбрасываем флаг
suspendFlag = false;
ta.append("Запуск потока\n");
//запускаем поток, который был остановлен (то есть сам себя)
notify();
}

}




Thread t;
TestThread tt;

private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
tt = new TestThread(jTextArea1);
t = new Thread(tt);
t.start();
}

private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) {
tt.suspendThread();
}

private void jButton3ActionPerformed(java.awt.event.ActionEvent evt) {
tt.resumeThread();
}
t
Категория: Java (Общие вопросы) | Просмотров: 2407 | Добавил: alex | Теги: поток, программист в Рыбинске, программирование, Java, мультипоточное, многопоточное | Рейтинг: 5.0/1
Категории раздела
Java (Общие вопросы) [17]
Java (Библиотека, пакеты Java) [17]
Java (Разработка программного обеспечения на Java) [5]
Java (Среда разработки NetBeans) [5]
JSF + PrimeFaces [21]
Java EE [11]
Разное [3]
Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0
Форма входа
Поиск
Календарь
«  Февраль 2013  »
ПнВтСрЧтПтСбВс
    123
45678910
11121314151617
18192021222324
25262728
Архив записей