ОТВЕРСТИЯ ОТ ПУЛЬ

  • Клиентские и серверные приложения взаимодействуют через соединение Socket.
  • Сокет представляет собой соединение между двумя приложениями, которые могут (или не могут) работать на двух разных физических машинах.
  • Клиент должен знать IP-адрес (или доменное имя) и номер TCP-порта серверного приложения.
  • Порт TCP — это 16-разрядное число без знака, назначенное определенному серверному приложению. Номера портов TCP позволяют различным клиентам подключаться к одному и тому же компьютеру, но взаимодействовать с разными приложениями, работающими на этом компьютере.
  • Номера портов от 0 до 1023 зарезервированы для «общеизвестных служб», включая HTTP, FTP, SMTP и т. д.
  • Клиент подключается к серверу, создавая сокет сервера Socket s = new Socket("127.0.0.1", 4200);
  • После подключения клиент может получать входные и выходные потоки из сокета. Это низкоуровневые потоки «соединения». носок.getInputStream(); носок.getOutputStream();
  • Чтобы прочитать текстовые данные с сервера, создайте BufferedReader, связанный с InputStreamReader, который связан с входным потоком из сокета.
  • InputStreamReader — это поток-мост, который принимает байты и преобразует их в текстовые (символьные) данные. Он используется в основном в качестве промежуточной цепочки между высокоуровневым BufferedReader и низкоуровневым входным потоком Socket.
  • Чтобы записать текстовые данные на сервер, создайте PrintWriter, связанный непосредственно с выходным потоком Socket. Вызовите методы print() или println() для отправки строк на сервер.
  • Серверы используют ServerSocket, который ожидает клиентских запросов на определенном номере порта.
  • Когда ServerSocket получает запрос, он «принимает» запрос, устанавливая соединение Socket с клиентом.
  • Поток со строчной буквой «t» — это отдельный поток выполнения в Java.
  • Каждый поток в Java имеет собственный стек вызовов.
  • Thread с заглавной «T» — это класс java.lang.Thread. Объект Thread представляет поток выполнения.
  • Потоку нужна работа. Задание потока — это экземпляр чего-то, что реализует интерфейс Runnable.
  • Интерфейс Runnable имеет только один метод run(). Это метод, который находится внизу нового стека вызовов. Другими словами, это первый метод, запускаемый в новом потоке.
  • Чтобы запустить новый поток, вам нужно, чтобы Runnable передавался конструктору потока.
  • Поток находится в состоянии NEW, когда вы создали экземпляр объекта Thread, но еще не вызвали start().
  • Когда вы запускаете поток (вызывая метод start() объекта Thread), создается новый стек с методом Runnable run() в нижней части стека. Теперь поток находится в состоянии RUNNABLE, ожидая выбора для запуска.
  • Поток считается ВЫПОЛНЯЕМЫМ, когда планировщик потоков JVM выбрал его в качестве текущего выполняющегося потока. На однопроцессорной машине может быть только один текущий поток.
  • Иногда поток может быть переведен из состояния RUNNING в состояние BLOCKED (временно неработоспособный). Поток может быть заблокирован из-за ожидания данных из потока, или из-за того, что он перешел в спящий режим, или из-за того, что он ожидает блокировки объекта.
  • Не гарантируется, что планирование потоков будет работать каким-либо конкретным образом, поэтому вы не можете быть уверены, что потоки будут чередоваться правильно. Вы можете повлиять на очередность, периодически переводя потоки в спящий режим.
  • Статический метод Thread.sleep() заставляет поток выйти из рабочего состояния, по крайней мере, на время, переданное методу сна. Thread.sleep(200) усыпляет поток на 200 миллисекунд.
  • Метод sleep() генерирует проверенное исключение (InterruptedException), поэтому все вызовы sleep() должны быть заключены в try/catch или объявлены.
  • Вы можете использовать функцию sleep(), чтобы убедиться, что все потоки имеют возможность запускаться, хотя нет никакой гарантии, что когда поток проснется, он перейдет к концу доступной для выполнения строки. Например, он может вернуться прямо на передний план. В большинстве случаев вызовы sleep() в нужное время — это все, что вам нужно для корректного переключения потоков.
  • Вы можете назвать поток, используя (еще один сюрприз) метод setName(). Все потоки получают имя по умолчанию, но явное имя может помочь вам отслеживать потоки, особенно если вы выполняете отладку с операторами печати.
  • У вас могут возникнуть серьезные проблемы с потоками, если два или более потока имеют доступ к одному и тому же объекту в куче.
  • Два или более потока, обращающихся к одному и тому же объекту, могут привести к повреждению данных, если один поток, например, выходит из рабочего состояния, все еще находясь в середине манипулирования критическим состоянием объекта.
  • Чтобы сделать ваши объекты потокобезопасными, решите, какие операторы следует рассматривать как один атомарный процесс. Другими словами, решите, какие методы должны выполняться до завершения, прежде чем другой поток введет тот же метод для того же объекта.
  • Используйте ключевое слово synchronized, чтобы изменить объявление метода, если вы хотите предотвратить вход в этот метод двух потоков.
  • Каждый объект имеет один замок с одним ключом для этого замка. Большую часть времени нас не волнует этот замок; блокировки вступают в игру только тогда, когда у объекта есть синхронизированные методы.
  • Когда поток пытается войти в синхронизированный метод, поток должен получить ключ для объекта (объекта, метод которого пытается запустить поток). Если ключ недоступен (потому что он уже есть у другого потока), поток переходит в своего рода зал ожидания, пока ключ не станет доступным.
  • Даже если объект имеет более одного синхронизированного метода, ключ остается только один. Как только какой-либо поток ввел синхронизированный метод для этого объекта, ни один поток не может ввести любой другой синхронизированный метод для того же объекта. Это ограничение позволяет защитить ваши данные, синхронизируя любой метод, который манипулирует данными.