5

5


Создание, использование объектов и время их жизни 37
(CAD), складского учета или управления воздушным движением — этот подход может оказаться чересчур ограниченным.
Второй путь — динамическое создание объектов в области памяти, называе¬мой «кучей» (heap). В таком случае количество объектов, их точные типы и время жизни остаются неизвестными до момента запуска программы. Все это определяется «на ходу» во время работы программы. Если вам понадобится но¬вый объект, вы просто создаете его в «куче» тогда, когда потребуется. Так как управление кучей осуществляется динамически, во время исполнения програм¬мы на выделение памяти из кучи требуется гораздо больше времени, чем при выделении памяти в стеке. (Для выделения памяти в стеке достаточно всего од¬ной машинной инструкции, сдвигающей указатель стека вниз, а освобождение осуществляется перемещением этого указателя вверх. Время, требуемое на вы¬деление памяти в куче, зависит от структуры хранилища.)
При использовании динамического подхода подразумевается, что объекты большие и сложные, таким образом, дополнительные затраты времени на выде¬ление и освобождение памяти не окажут заметного влияния на процесс их соз¬дания. Потом, дополнительная гибкость очень важна для решения основных за¬дач программирования.
В Java используется исключительно второй подход . Каждый раз при созда¬нии объекта используется ключевое слово new для построения динамического экземпляра.
Впрочем, есть и другой фактор, а именно время жизни объекта. В языках, поддерживающих создание объектов в стеке, компилятор определяет, как долго используется объект, и может автоматически уничтожить его. Однако при соз¬дании объекта в куче компилятор не имеет представления о сроках жизни объ¬екта. В языках, подобных С++, уничтожение объекта должно быть явно оформ¬лено в программе; если этого не сделать, возникает утечка памяти (обычная проблема в программах С++). В Java существует механизм, называемый сборкой мусора; он автоматически определяет, когда объект перестает использоваться, и уничтожает его. Сборщик мусора очень удобен, потому что он избавляет про¬граммиста от лишних хлопот. Что еще важнее, сборщик мусора дает гораздо большую уверенность в том, что в вашу программу не закралась коварная про¬блема утечки памяти (которая «поставила на колени» не один проект на языке С++).
В Java сборщик мусора спроектирован так, чтобы он мог самостоятельно ре¬шать проблему освобождения памяти (это не касается других аспектов завер¬шения жизни объекта). Сборщик мусора «знает», когда объект перестает ис¬пользоваться, и применяет свои знания для автоматического освобождения памяти. Благодаря этому факту (вместе с тем, что все объекты наследуются от единого базового класса Object и создаются только в куче) программирова¬ние на Java гораздо проще, чем программирование на С++. Разработчику при¬ходится принимать меньше решений и преодолевать меньше препятствий.
Обработка исключений: борьба с ошибками
С первых дней существования языков программирования обработка ошибок была одним из самых каверзных вопросов. Разработать хороший механизм об¬работки ошибок очень трудно, поэтому многие языки попросту игнорируют эту проблему, оставляя ее разработчикам программных библиотек. Последние пре¬доставляют половинчатые решения, которые работают во многих ситуациях, но которые часто можно попросту обойти (как правило, просто не обращая на них внимания). Главная проблема многих механизмов обработки исключений состоит в том, что они полагаются на добросовестное соблюдение программи¬стом правил, выполнение которых не обеспечивается языком. Если програм¬мист проявит невнимательность — а это часто происходит при спешке в рабо¬те — он может легко забыть об этих механизмах.
Механизм обработки исключений встраивает обработку ошибок прямо в язык программирования или даже в операционную систему. Исключе¬ние представляет собой объект, генерируемый на месте возникновении ошибки, который затем может быть «перехвачен» подходящим обработчиком исключе¬ний, предназначенным для ошибок определенного типа. Обработка исключе¬ний словно определяет параллельный путь выполнения программы, вступаю¬щий в силу, когда что-то идет не по плану. И так как она определяет отдельный путь исполнения, код обработки ошибок не смешивается с обычным кодом. Это упрощает написание программ, поскольку вам не приходится постоянно прове¬рять возможные ошибки. Вдобавок исключение не похоже на числовой код ошибки, возвращаемый методом, или на флаг, устанавливаемый в случае про¬блемной ситуации, — последние могут быть проигнорированы. Исключение же нельзя пропустить, оно обязательно будет где-то обработано. Наконец, исклю¬чения дают возможность восстановить нормальную работу программы после неверной операции. Вместо того, чтобы просто завершить программу, можно исправить ситуацию и продолжить ее выполнение; тем самым повышается на¬дежность программы.
Механизм обработки исключений Java выделяется среди остальных, потому что он был встроен в язык с самого начала, и разработчик обязан его использо¬вать. Если он не напишет кода для подобаюгцей обработки исключений, компи¬лятор выдаст ошибку. Подобный последовательный подход иногда заметно уп¬рощает обработку ошибок.
Стоит отметить, что обработка исключений не является особенностью объ¬ектно-ориентированного языка, хотя в этих языках исключение обычно пред¬ставлено объектом. Такой механизм существовал и до возникновения объект¬но-ориентированного программирования.
Параллельное выполнение
Одной из фундаментальных концепций программирования является идея од¬новременного выполнения нескольких операции. Многие задачи требуют, что¬бы программа прервала свою текущую работу, решила какую-то другую задачу, а затем вернулась в основной процесс. Проблема решалась разными способами.
На первых порах программисты, знающие машинную архитектуру, писали про¬цедуры обработки прерываний, то есть приостановка основного процесса вы¬полнялась на аппаратном уровне. Такое решение работало неплохо, но оно было сложным и немобильным, что значительно усложняло перенос подобных программ на новые типы компьютеров.
Иногда прерывания действительно необходимы для выполнения операций задач, критичных по времени, но существует целый класс задач, где просто нужно разбить задачу на несколько раздельно выполняемых частей так, чтобы программа быстрее реагировала на внешние воздействия. Эти раздельно вы¬полняемые части программы называются потоками, а весь принцип получил название многозадачности, или параллельных вычислений. Часто встречающий¬ся пример многозадачности — пользовательский интерфейс. В программе, раз¬битой на потоки, пользователь может нажать кнопку и получить быстрый от¬вет, не ожидая, пока программа завершит текущую операцию.
Обычно потоки всего лишь определяют схему распределения времени на од¬нопроцессорном компьютере. Но если операционная система поддерживает многопроцессорную обработку, каждый поток может быть назначен на отдель¬ный процессор; так достигается настоящий параллелизм. Одно из удобных свойств встроенной в язык многозадачности состоит в том, что программисту не нужно знать, один процессор в системе или несколько. Программа логически разделяется на потоки, и если машина имеет больше одного процессора, она ис¬полняется быстрее, без каких-либо специальных настроек.
Все это создает впечатление, что потоки использовать очень легко. Но тут кроется подвох: совместно используемые ресурсы. Если несколько потоков пы¬таются одновременно получить доступ к одному ресурсу, возникают проблемы. Например, два процесса не могут одновременно посылать информацию на принтер. Для предотвращения конфликта совместные ресурсы (такие как принтер) должны блокироваться во время использования. Поток блокирует ре¬сурс, завершает свою операцию, а затем снимает блокировку для того, чтобы кто-то еще смог получить доступ к ресурсу.
Поддержка параллельного выполнения встроена в язык Java, а с выходом Java SE5 к ней добавилась значительная поддержка на уровне библиотек.
Java и Интернет
Если Java представляет собой очередной язык программирования, возникает вопрос: чем же он так важен и почему он преподносится как революционный шаг в разработке программ? С точки зрения традиционных задач программиро¬вания ответ очевиден не сразу. Хотя язык Java пригодится и при построении автономных приложений, самым важным его применением было и остается программирование для сети World Wide Web.
Что такое Веб?
На первый взгляд Веб выглядит довольно загадочно из-за обилия новомод¬ных терминов вроде «серфинга», «присутствия» и «домашних страниц». Чтобы понять, что же это такое, полезно представить себе картину в целом — но сначала необходимо разобраться во взаимодействии клиент/серверных систем, которые представляют собой одну из самых сложных задач компьютерных вычислений.
Вычисления «клиент/сервер»
Основная идея клиент/серверных систем состоит в том, что у вас существует централизованное хранилище информации — обычно в форме базы данных — и эта информация доставляется по запросам каких-либо групп людей или ком¬пьютеров. В системе клиент/сервер ключевая роль отводится централизованно¬му хранилищу информации, которое обычно позволяет изменять данные так, что эти изменения будут быстро переданы пользователям информации. Все вместе:. хранилище информации, программы, распределяющие информацию, и компьютер, на котором хранятся программы и данные — называется сервером. Программное обеспечение на машине пользователя, которое устанавливает связь с сервером, получает информацию, обрабатывает ее и затем отображает соответствующим образом, называется клиентом.
Таким образом, основная концепция клиент/серверных вычислений не так уж сложна. Проблемы возникают из-за того, что получить доступ к серверу пы¬таются сразу несколько клиентов одновременно. Обычно для решения привле¬кается система управления базой данных, и разработчик пытается «оптимизи¬ровать» структуру данных, распределяя их по таблицам. Дополнительно система часто дает возможность клиенту добавлять новую информацию на сер¬вер. А это значит, что новая информация клиента должна быть защищена от по¬тери во время сохранения в базе данных, а также от возможности ее перезаписи данными другого клиента. (Это называется обработкой транзакций.) При изме¬нении клиентского программного обеспечения необходимо не только скомпи¬лировать и протестировать его, но и установить на клиентских машинах, что может обойтись гораздо дороже, чем можно представить. Особенно сложно ор¬ганизовать поддержку множества различных операционных систем и компью¬терных архитектур. Наконец, необходимо учитывать важнейший фактор произ¬водительности: к серверу одновременно могут поступать сотни запросов, и малейшая задержка грозит серьезными последствиями. Для уменьшения за¬держки программисты стараются распределить вычисления, зачастую даже проводя их на клиентской машине, а иногда и переводя на дополнительные сер¬верные машины, используя так называемое связующее программное обеспечение (middleware). (Программы-посредники также упрощают сопровождение про¬грамм.)