1 / 24

Объектно-ориентированное программирование

Объектно-ориентированное программирование. Стандартные библиотеки ввода-вывода Сетевое взаимодействие. Когда Java программа начинает исполняться, в ней работает единственный программный поток, выполнение которого начинается с метода main() стартового класса.

sorley
Download Presentation

Объектно-ориентированное программирование

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Объектно-ориентированное программирование Стандартные библиотеки ввода-вывода Сетевое взаимодействие

  2. Когда Java программа начинает исполняться, в ней работает единственный программный поток, выполнение которого начинается с метода main() стартового класса. В процессе работы программе могут потребоваться дополнительные (рабочие) программные потоки. Для их создания служит класс Thread. Есть два способа создания рабочего потока: 1. создать подкласс класса Thread, в котором переопределить метод run() 2. создать реализацию интерфейса Runnable, в которой переопределить метод run(), а затем на основе этой реализации создать экземпляр класса Thread, передав реализацию Runnable через параметр конструктора. В каждом из названных случаев получается экземпляр класса Thread, у которого можно вызвать метод start()для запуска нового рабочего потока, который начнет выполнение инструкций метода run().

  3. static class ClientServantimplements Runnable { private Socket m_socket; private int m_iID; public ClientServant(Socket socket, int iID) { m_socket = socket; m_iID = iID; } public void run() { try { InputStream is = m_socket.getInputStream(); OutputStream os = m_socket.getOutputStream(); try { ... } finally { closeStream(os); closeStream(is); m_socket.close(); } } catch (Exception ex) { System.err.println(MessageFormat.format( "client {0} terminated with error: {1}", new Object []{String.valueOf(m_iID), ex})); } } Изменим код сервера (начало)

  4. public class TCPServer { ... public static void main(String [] args) { try { ServerSocket serverSocket = new ServerSocket(PORT); while (true) { Socket socket = serverSocket.accept(); st_counter++; ClientServant servant = new ClientServant(socket, st_counter); new Thread(servant).start(); } } catch (Exception ex) { ex.printStackTrace(); } } ... } Изменим код сервера (конец)

  5. Теперь сервер способен одновременно обслуживать несколько клиентов, поскольку каждое подключение обслуживается в собственном программном потоке. Для проверки этого можно использовать команду telnet localhost 40000 Можно убедиться, что теперь можно запустить несколько сеансов работы с сервером, и в каждом сеансе можно независимо вычислять арифметические выражения.

  6. public static void main(String [] args) { try { // InetAddress address = InetAddress.getLocalHost(); // InetAddress address = InetAddress.getByName("localhost"); InetAddress address = InetAddress.getByAddress(new byte [] {127,0,0,1}); Socket socket = new Socket(address, TCPServer.PORT); try { new StreamCopier(socket.getInputStream(), System.out).start(); new StreamCopier(System.in, socket.getOutputStream()).start(); } finally { socket.close(); } } catch (Exception ex) { ex.printStackTrace(); } } Код установления соединения на стороне клиента похож на код серверной стороны:

  7. Наблюдение: на клиентской стороне входной и выходной потоки сокета как правило следует обслуживать одновременно, то есть требуется два потока: public static void main(String [] args) { try { // InetAddress address = InetAddress.getLocalHost(); // InetAddress address = InetAddress.getByName("localhost"); InetAddress address = InetAddress.getByAddress(new byte [] {127,0,0,1}); Socket socket = new Socket(address, TCPServer.PORT); try { new StreamCopier(socket.getInputStream(), System.out).start(); new StreamCopier(System.in, socket.getOutputStream()).start(); } finally { socket.close(); } } catch (Exception ex) { ex.printStackTrace(); } }

  8. class StreamCopier extends Thread { private InputStream m_in; private OutputStream m_out; public StreamCopier(InputStream in, OutputStream out) { m_in = in; m_out = out; } @Override public void run() { try { FileUtils.copyStream(m_in, m_out); } catch (Exception ex) { ex.printStackTrace(); } } } Так устроен класс, отвечающий за обслуживание потоков:

  9. class FileUtils { private final static int BUFFER_SIZE = 16 * 1024; public static int copyStream(InputStream in, OutputStream out) throws IOException { int iBytesCopied = 0; try { byte [] buffer = new byte[BUFFER_SIZE]; while (true) { int count = in.read(buffer); if (count < 0) { break; } else if (count > 0) { iBytesCopied += count; out.write(buffer, 0, count); } } } finally { close(in); close(out); } return iBytesCopied; } } Так устроен код, копирующий информацию из одного потока в другой:

  10. Демонстрация TCPClient и TCPClientGUI

  11. Создавать программы, использующие несколько программных потоков, сложнее, нежели однопоточные программы. Причина в трех основных сложностях, приводящих к ошибкам: 1. Совместное одновременное использование разделяемых ресурсов из нескольких потоков 2. Синхронизация потоков 3. Взаимная блокировка потоков

  12. class TestThread implements Runnable { private int m_iId; private String m_strValue; TestThread(int iId, String strValue) { m_iId = iId; m_strValue = strValue; } public void run() { System.out.println(""+m_iId+": value before: "+m_strValue); String strResult = SharedBuffer.reverse(m_strValue); System.out.println(""+m_iId+": value after: "+strResult); } } Пример использования разделяемого ресурса (начало):

  13. public class SharedBuffer { public static void main(String [] args) { new Thread(new TestThread(1, "Some text")).start(); new Thread(new TestThread(2, "Another text string")).start(); } static private StringBuffer m_buffer = new StringBuffer(); static public String reverse(String strValue) { m_buffer.setLength(0); for (int iIdx = strValue.length() - 1; iIdx >= 0; iIdx--) { m_buffer.append(strValue.charAt(iIdx)); try { Thread.sleep(50); } catch (InterruptedException ex) { } } return m_buffer.toString(); } } Пример использования разделяемого ресурса (конец):

  14. 2: value before: Another text string 1: value before: Some text 1: value after: tnxeitr tse motxS 2: value after: tnxeitr tse motxSet rehtonA Результат работы этого кода: Причина такого поведения программы в том, что в двух потоках производится одновременная модификация одного и того же разделяемого буфера. Как решить эту проблему?

  15. public class SharedBuffer { public static void main(String [] args) { new Thread(new TestThread(1, "Some text")).start(); new Thread(new TestThread(2, "Another text string")).start(); } static public String reverse(String strValue) { StringBuffer buffer = new StringBuffer(); for (int iIdx = strValue.length() - 1; iIdx >= 0; iIdx--) { buffer.append(strValue.charAt(iIdx)); try { Thread.sleep(50); } catch (InterruptedException ex) { } } return buffer.toString(); } } Простейший способ решения проблемы – избавиться от разделения ресурса:

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

  17. public class SharedBuffer { public static void main(String [] args) { ... } static private StringBuffer m_buffer = new StringBuffer(); static private boolean m_bLocked = false; static public String reverse(String strValue) { while (m_bLocked) { try { Thread.sleep(50); } catch (InterruptedException ex) {} } m_bLocked = true; m_buffer.setLength(0); for (int iIdx = strValue.length() - 1; iIdx >= 0; iIdx--) { m_buffer.append(strValue.charAt(iIdx)); try { Thread.sleep(50); } catch (InterruptedException ex) {} } m_bLocked = false; return m_buffer.toString(); } } Попробуем защитить разделяемый ресурс:

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

  19. public class SharedBuffer { public static void main(String [] args) { ... } static private StringBuffer m_buffer = new StringBuffer(); static private boolean m_bLocked = false; static public String reverse(String strValue) { while (m_bLocked) { try { Thread.sleep(50); } catch (InterruptedException ex) {} } m_bLocked = true; m_buffer.setLength(0); for (int iIdx = strValue.length() - 1; iIdx >= 0; iIdx--) { m_buffer.append(strValue.charAt(iIdx)); try { Thread.sleep(50); } catch (InterruptedException ex) {} } m_bLocked = false; return m_buffer.toString(); } } Почему этот код не является корректным?

  20. public class SharedBuffer { public static void main(String [] args) { ... } static private StringBuffer m_buffer = new StringBuffer(); static private boolean m_bLocked = false; static public String reverse(String strValue) { while (m_bLocked) { try { Thread.sleep(50); } catch (InterruptedException ex) {} } m_bLocked = true; m_buffer.setLength(0); for (int iIdx = strValue.length() - 1; iIdx >= 0; iIdx--) { m_buffer.append(strValue.charAt(iIdx)); try { Thread.sleep(50); } catch (InterruptedException ex) {} } m_bLocked = false; return m_buffer.toString(); } } Почему этот код не является корректным? Между проверкой на доступность и установкой блокировки есть окно. Здесь блокировка уже снята, а результат еще не возвращен. В случае runtime исключения блокировка не будет снята!

  21. Выводы: 1. Перед доступом к разделяемому ресурсу надо уметь ЗА ОДНУ ОПЕРАЦИЮ проверять доступность ресурса и выполнять его блокировку. 2. Блок доступа к ресурсу следует контролировать при помощи конструкции try/finally, и в секции finally следует всегда снимать блокировку ресурса.

  22. В простейшем случае для выполнения блокировки ресурса Java предлагает конструкцию synchronized: synchronized (<объект синхронизации>) { ... // защищаемый код } Конструкция synchronized работает так: 1. проверяется, есть ли в данный момент поток, работающий внутри (возможно другой) секции synchronized с тем же объектом синхронизации. 2. если такой поток есть, то выполнение текущего потока останавливается до тех пор, пока не останется ни одного потока, работающего внутри секции synchronized с тем же объектом синхронизации. 3. продолжается выполнение защищаемого кода. Важно: Все три названных шага работают как одна атомарная операция! Таким образом, JVM гарантирует, что не возникнет ситуации, когда два потока будут одновременно выполнять защищаемый код (даже в разных блоках synchronized) синхронизируемый по одному и тому же объекту.

  23. Есть специальный случай использования инструкции synchronized в качестве модификатора метода: synchronized void doSomething() { ... // защищаемый код } Такой код эквивалентен следующему: void doSomething() { synchronized(this) { ... // защищаемый код } }

  24. public class SharedBuffer { public static void main(String [] args) { ... } static private StringBuffer m_buffer = new StringBuffer(); static public String reverse(String strValue) { synchronized(m_buffer) { m_buffer.setLength(0); for (int iIdx = strValue.length() - 1; iIdx >= 0; iIdx--) { m_buffer.append(strValue.charAt(iIdx)); try { Thread.sleep(50); } catch (InterruptedException ex) { } } return m_buffer.toString(); } } } Так выглядит правильная версия кода с использованием synchronized

More Related