завершение обычного потока




Поток-демон (из-за вызова метода sleep(10000)) не успел завершить выполнение своего кода до завершения основного потока приложения, связанного с методом main(). Базовое свойство потоков-демонов заключается в возможности основного потока приложения завершить выполнение потока-демона (в отличие от обычных потоков) с окончанием кода метода main(), не обращая внимания на

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

 

34. Методы synchronized. Инструкция synchronized

Очень часто возникает ситуация, когда несколько потоков, обращающихся к некоторому общему ресурсу, начинают мешать друг другу; более того, они могут повредить этот общий ресурс. Например, когда два потока записывают информацию в файл/объект/поток. Для предотвращения такой ситуации может использоваться ключевое слово synchronized. Синхронизации не требуют только атомарные процессы по записи/чтению, не превышающие по объему 32 бит.

В качестве примера будет рассмотрен процесс записи информации в файл двумя конкурирующими потоками. В методе main() классa SynchroThreads создаются два потока. В этом же методе создается экземпляр класса Synchro, содержащий поле типа FileWriter, связанное с файлом на диске. Экземпляр Synchro передается в качестве параметра обоим потокам. Первый поток записывает строку методом writing() в экземпляр класса Synchro. Второй поток также пытается сделать запись строки в тот же самый объект Synchro. Для избежания одновременной записи такие методы объявляются как synchronized.

Синхронизированный метод изолирует объект, после чего объект становится недоступным для других потоков. Изоляция снимается, когда поток полностью выполнит соответствующий метод. Другой способ снятия изоляции – вызов метода wait() из изолированного метода.

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

/* пример # 8: синхронизация записи информации в файл: MyThread.java:

Synchro.java: SynchroThreads.java */

package chapt14;

import java.io.*;

public class Synchro {

private FileWriter fileWriter;

public Synchro(String file) throws IOException

{

fileWriter = new FileWriter(file, true);

}

public void close() {

try {

fileWriter.close();

}

catch (IOException e) {

e.printStackTrace();

}

}

public synchronized void writing(String str, int i) {

try {

System. out. print(str + i);

fileWriter.append(str + i);

Thread. sleep ((long)(Math. random () * 50));

System. out. print("->" + i + " ");

fileWriter.append("->" + i + " ");

}

catch (IOException e) {

System. err. print("ошибка файла");

e.printStackTrace();

}

catch (InterruptedException e) {

System. err. print("ошибка потока");

e.printStackTrace();

}

}

}

package chapt14;

public class MyThread extends Thread {

private Synchro s;

public MyThread(String str, Synchro s)

{

super (str);

this. s = s;

}

public void run()

{

for (int i = 0; i < 5; i++) {

s.writing(getName(), i);

}

}

}

package chapt14;

import java.io.*;

public class SynchroThreads {

public static void main(String[] args)

{

try {

Synchro s = new Synchro("c:\\temp\\data.txt");

MyThread t1 = new MyThread("First", s);

MyThread t2 = new MyThread("Second", s);

t1.start();

t2.start();

t1.join();

t2.join();

s.close();

}

catch (IOException e) {

System. err. print("ошибка файла");

e.printStackTrace();

}

catch (InterruptedException e) {

System. err. print("ошибка потока");

e.printStackTrace();

}

}

}

В результате в файл будет выведено:

First0->0 Second0->0 First1->1 Second1->1 First2->2

Second2->2 First3->3 Second3->3 First4->4 Second4->4

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

Вывод в этом случае может быть, например, следующим:

First0Second0->0 Second1->0 First1->1 First2->1 Second2->2

First3->3 First4->2 Second3->3 Second4->4 ->4

Инструкция synchronized

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

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

/* пример # 9: блокировка объекта потоком: TwoThread.java */

package chapt14;

public class TwoThread {

public static void main(String args[])

{

final StringBuffer s = new StringBuffer();

new Thread() {

public void run() {

int i = 0;

synchronized (s) {

while (i++ < 3) {

s.append("A");

try {

sleep (100);

}

catch (InterruptedException e)

{

System. err. print(e);

}

System. out. println(s);

}

} //конец synchronized

}

}.start();

new Thread() {

public void run() {

int j = 0;

synchronized (s) {

while (j++ < 3) {

s.append("B");

System. out. println(s);

}

} //конец synchronized

}

}.start();

}

}

В результате компиляции и запуска будет, скорее всего (так как и второй поток может заблокировать объект первым), выведено:

A

AA

AAA

AAAB

AAABB

AAABBB

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

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

В следующем примере рассмотрено взаимодействие методов wait() и notify() при освобождении и возврате блокировки в synchronized блоке.

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

Метод wait(), вызванный внутри синхронизированного блока или метода, останавливает выполнение текущего потока и освобождает от блокировки захваченный объект, в частности объект lock. Возвратить блокировку объекта потоку можно вызовом метода notify() для конкретного потока или

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

 

35. Состояния потока

В классе Thread объявлено внутреннее перечисление State, простейшее применение элементов которого призвано помочь в отслеживании состояний потока в процессе функционирования приложения и, как следствие, в улучшении управления им.

/* пример # 11: состояния NEW, RUNNABLE, TIMED_WAITING, TERMINATED:

ThreadTimedWaitingStateTest.java */

package chapt14;

public class ThreadTimedWaitingStateTest extends Thread {

public void run() {

try {

Thread. sleep (50);

}

catch (InterruptedException e) {

System. err. print("ошибка потока");

}

}

public static void main(String [] args){

try {

Thread thread = new ThreadTimedWaitingStateTest();

// NEW – поток создан, но ещё не запущен

System. out. println("1: " + thread.getState());

thread.start();

// RUNNABLE – поток запущен

System. out. println("2: " + thread.getState());

Thread.sleep(10);

// TIMED_WAITING

// поток ждет некоторое время окончания работы другого потока

System.out.println("3: " + thread.getState());

thread.join();

// TERMINATED – поток завершил выполнение

System.out.println("4: " + thread.getState());

}

catch (InterruptedException e) {

System.err.print("ошибка потока");

}

}

}

В результате компиляции и запуска будет выведено:

NEW

RUNNABLE

TIMED_WAITING

TERMINATED

 

 

36. Сокетные соединения по протоколу TCP/IP

Сокеты (сетевые разъёмы) — это логическое понятие, соответствующее разъёмам, к которым подключены сетевые компьютеры и через которые осуществляется двунаправленная поточная передача данных между компьютерами. Сокет определяется номером порта и IP-адресом. При этом IP-адрес используется для идентификации компьютера, номер порта – для идентификации процесса, работающего на компьютере. Когда одно приложение знает сокет другого, создается сокетное протоколо-ориентированное соединение по протоколу TCP/IP. Клиент пытается соединиться с сервером, инициализируя сокетное соединение. Сервер прослушивает сообщение и ждет, пока клиент не свяжется с ним. Первое сообщение, посылаемое клиентом на сервер, содержит сокет клиента. Сервер, в свою очередь, создает сокет, который будет использоваться для связи с клиентом, и посылает его клиенту с первым сообщением. После этого устанавливается коммуникационное соединение.

Сокетное соединение с сервером создается клиентом с помощью объекта класса Socket. При этом указывается IP-адрес сервера и номер порта. Если указано символьное имя домена, то Java преобразует его с помощью DNS-сервера к IP-адресу. Например, если сервер установлен на этом же компьютере, соединение с сервером можно установить из приложения клиента с помощью инструкции:

Socket socket = new Socket(" ИМЯ_СЕРВЕРА ", 8030);

Сервер ожидает сообщения клиента и должен быть заранее запущен с указанием определенного порта. Объект класса ServerSocket создается с указанием конструктору номера порта и ожидает сообщения клиента с помощью метода accept() класса ServerSocket, который возвращает сокет клиента:

ServerSocket server = new ServerSocket(8030);

Socket socket = server.accept();

Таким образом, для установки необходимо установить IP-адрес и номер порта сервера, IP-адрес и номер порта клиента. Обычно порт клиента и сервера устанавливаются одинаковыми. Клиент и сервер после установления сокетного соединения могут получать данные из потока ввода и записывать данные в поток вывода с помощью методов getInputStrеam() и getOutputStrеam() или к

PrintStream для того, чтобы программа могла трактовать поток как выходные файлы.

В следующем примере для отправки клиенту строки "привет!" сервер вызывает метод getOutputStream() класса Socket. Клиент получает данные от сервера с помощью метода getInputStream(). Для разъединения клиента и сервера после завершения работы сокет закрывается с помощью метода close() класса Socket. В данном примере сервер отправляет клиенту строку "при-

вет!", после чего разрывает связь.

/* пример # 5: передача клиенту строки: MyServerSocket.java */

package chapt15;

import java.io.*;

import java.net.*;

public class MyServerSocket {

public static void main(String[] args)

{

Socket s = null;

try { // отправка строки клиенту

//создание объекта и назначение номера порта

ServerSocket server = new ServerSocket(8030);

s = server.accept(); //ожидание соединения

PrintStream ps = new PrintStream(s.getOutputStream());

// помещение строки "привет!" в буфер

ps.println("привет!");

// отправка содержимого буфера клиенту и его очищение

ps.flush();

ps.close();

}

catch (IOException e) {

System. err. println("Ошибка: " + e);

}

finally {

if (s!= null)

s.close(); // разрыв соединения

}

}

}

 

37. Многопоточность

Сервер должен поддерживать многопоточность, иначе он будет не в состоянии обрабатывать несколько соединений одновременно. В этом случае сервер содержит цикл, ожидающий нового клиентского соединения. Каждый раз, когда клиент просит соединения, сервер создает новый поток. В следующем примере создается класс ServerThread, расширяющий класс Thread, и используется

затем для соединений с многими клиентами, каждый в своем потоке.

/* пример # 7: сервер для множества клиентов: NetServerThread.java */

package chapt15;

import java.io.*;

import java.net.*;

public class NetServerThread {

public static void main(String[] args) {

try {

ServerSocket serv = new ServerSocket(8071);

System. out. println("initialized");

while (true) {

//ожидание клиента

Socket sock = serv.accept();

System. out. println(sock.getInetAddress().getHostName()+ " connected");

/*создание отдельного потока для обмена данными с соединившимся клиентом*/

ServerThread server = new ServerThread(sock);

server.start(); //запуск потока

}

}

catch (IOException e) {

System. err. println(e);

}

}

}

class ServerThread extends Thread {

private PrintStream os; //передача

private BufferedReader is; //чтение

private InetAddress addr; //адрес клиента

public ServerThread(Socket s) throws IOException

{

os = new PrintStream(s.getOutputStream());

is = new BufferedReader(new InputStreamReader(s.getInputStream()));

addr = s.getInetAddress();

}

public void run() {

int i = 0;

String str;

try {

while ((str = is.readLine())!= null)

{

if ("PING".equals(str))

os.println("PONG "+ ++i);

System. out. println("PING-PONG" + i + " with " + addr.getHostName());

}

}

catch (IOException e) {

//если клиент не отвечает, соединение с ним разрывается

System. out. println("Disconnect");

}

finally {

disconnect(); //уничтожение потока

}

}

public void disconnect() {

try {

System. out. println(addr.getHostName()+ " disconnected");

os.close();

is.close();

}

catch (IOException e) {

e.printStackTrace();

}

finally {

this. interrupt();

}

}

}

Сервер передает сообщение, посылаемое клиенту. Для клиентских приложений поддержка многопоточности также необходима. Например, один поток ожидает выполнения операции ввода/вывода, а другие потоки выполняют свои функции. Сервер должен быть инициализирован до того, как клиент попытается осуществить сокетное соединение. При этом может быть использован IP-адрес локального компьютера.

 

 

38. Датаграммы и протокол UDP

UDP (User Datagram Protocol) не устанавливает виртуального соединения и не гарантирует доставку данных. Отправитель просто посылает пакеты по указанному адресу; если отосланная информация была повреждена или вообще не дошла, отправитель об этом даже не узнает. Однако достоинством UDP является высокая скорость передачи данных. Данный протокол часто используется при трансляции аудио- и видеосигналов, где потеря небольшого количества данных не может привести к серьезным искажениям всей информации. По протоколу UDP данные передаются пакетами. Пакетом в этом случае UDP является объект класса DatagramPacket. Этот класс содержит в себе передаваемые данные, представленные в виде массива байт. Конструкторы класса:

DatagramPacket(byte[] buf, int length)

DatagramPacket(byte[] buf, int length,



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2016-02-16 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: