Создание, анализ ирефакторинг
Скачать 3.16 Mb.
|
Строка Описание 10 Создание экземпляра ClassWithThreadingProblem. Обратите внимание на необхо- димость использования ключевого слова final, так как ниже объект используется в анонимном внутреннем классе 12–16 Создание анонимного внутреннего класса, использующего экземпляр ClassWith- ThreadingProblem 18 Код выполняется количество раз, достаточное для демонстрации его некоррект- ности, но так, чтобы он не выполнялся «слишком долго». Необходимо выдержать баланс между двумя целями; сбои должны выявляться за разумное время. Подо- брать нужное число непросто, хотя, как мы вскоре увидим, его можно заметно сократить 19 Сохранение начального значения. Мы пытаемся доказать, что код ClassWith- ThreadingProblem некорректен. Если тест проходит, то он доказывает, что код некорректен. Если тест не проходит, то он не доказывает ничего 20 Итоговое значение должно быть на два больше текущего 22–23 Создание двух потоков, использующих объект, который был создан в строках 12– 16. Два потока, пытающихся использовать один экземпляр ClassWithThreading- Problem, могут помешать друг другу; эту ситуацию мы и пытаемся воспроизвести. 24–25 Запуск двух потоков 382 Тестирование многопоточного кода 383 Строка Описание 26–27 Ожидание завершения обоих потоков с последующей проверкой результатов 29 Сохранение итогового значения 31–32 Отличается ли значение endingId от ожидаемого? Если отличается, вернуть признак завершения теста – доказано, что код работает некорректно. Если нет, попробовать еще раз 35 Если управление передано в эту точку, нашим тестам не удалось доказать не- корректность кода за «разумное» время. Либо код работает корректно, либо ко- личество итераций было недостаточным для возникновения сбойной комбинации Бесспорно, этот тест создает условия для выявления проблем многопоточного обновления . Но проблема встречается настолько редко, что в подавляющем боль- шинстве случаев тестирование ее попросту не выявит . В самом деле, для сколько- нибудь статистически значимого выявления проблемы количество итераций должно превышать миллион . Несмотря на это, за десять выполнений цикла из 1 000 000 итераций проблема была обнаружена всего один раз . Это означает, что для надежного выявления сбоев количество итераций должно составлять около 100 миллионов . Как долго вы готовы ждать? Даже если тест будет надежно выявлять сбои на одном компьютере, вероятно, его придется заново настраивать с другими параметрами для выявления сбоев на другом компьютере, операционной системе или версии JVM . А ведь мы взяли очень простую задачу . Если нам не удается легко продемон- стрировать некорректность кода в тривиальной ситуации, как обнаружить по- настоящему сложную проблему? Как ускорить выявление этого простейшего сбоя? И что еще важнее, как напи- сать тесты, демонстрирующие сбои в более сложном коде? Как узнать, что код некорректен, если мы даже не знаем, где искать? Несколько возможных идей . Тестирование методом Монте-Карло. Сделайте тесты достаточно гибкими, что- бы вы могли легко вносить изменения в их конфигурацию . Повторяйте тесты сно- ва и снова (скажем, на тестовом сервере) со случайным изменением параметров . Если в ходе тестирования будет обнаружена ошибка, значит, код некорректен . Начните писать эти тесты на ранней стадии, чтобы как можно ранее начать их выполнение на сервере непрерывной интеграции . Не забудьте сохранить набор условий, при котором был выявлен сбой . Выполняйте тесты на каждой целевой платформе разработки . Многократно . Непрерывно . Чем продолжительнее тесты работают без сбоев, тем выше ве- роятность, что: • код продукта корректен, либо • тестирования недостаточно для выявления проблем . Запускайте тесты на машинах с разной нагрузкой . Если вы сможете имитировать нагрузку, близкую к среде реальной эксплуатации, сделайте это . 383 384 Приложение А . Многопоточность II Но даже после выполнения всех этих действий вероятность обнаружения много- поточных проблем в вашем коде оставляет желать лучшего . Самыми коваными оказываются проблемы, возникающие при определенных комбинациях условий, встречающихся один раз на миллиард . Такие проблемы – настоящий бич слож- ных систем . Средства тестирования многопоточного кода Компания IBM создала программу ConTest 1 , которая особым образом готовит классы для повышения вероятности сбоев в потоково-небезопасном коде . Мы не связаны ни с IBM, ни с группой разработки ConTest . Об этой программе нам рассказал один из коллег . Оказалось, что всего несколько минут использо- вания ConTest радикально повышают вероятность выявления многопоточных сбоев . Тестирование с использованием ConTest проходит по следующей схеме: Напишите тесты и код продукта . Проследите за тем, чтобы тесты были специ- ально спроектированы для имитации обращений от многих пользователей при переменной нагрузке, как упоминалось ранее . Проведите инструментовку кода тестов и продукта при помощи ConTest . Выполните тесты . Если вы помните, ранее сбой выявлялся примерно один раз за десять миллионов запусков . После инструментовки кода в ConTest сбои стали выявляться один раз за тридцать запусков . Таким образом, сбои в адаптированных классах стали вы- являться намного быстрее и надежнее . Заключение В этой главе мы предприняли очень краткое путешествие по огромной, нена- дежной территории многопоточного программирования . Наше знакомство с этой темой нельзя назвать даже поверхностным . Основное внимание уделялось мето- дам поддержания чистоты многопоточного кода, но если вы собираетесь писать многопоточные системы, вам придется еще многому научиться . Мы рекомендуем начать с замечательной книги Дуга Ли «Concurrent Programming in Java: Design Principles and Patterns» [Lea99, p . 191] . В этой главе рассматривались опасности многопоточного обновления, а также ме- тоды чистой синхронизации и блокировки, которые могут их предотвратить . Мы 1 http://www .haifa .ibm .com/projects/veri�cation/contest/index .html 384 Полные примеры кода 385 обсудили, как потоки могут повысить производительность систем с интенсивным вводом/выводом, а также конкретные приемы повышения производительности . Также была рассмотрена взаимная блокировка и чистые способы ее предотвраще- ния . Приложение завершается описанием стратегий выявления многопоточных проблем посредством инструментовки кода . Полные примеры кода Однопоточная реализация архитектуры «клиент/сервер» листинг А .3 . Server.java package com.objectmentor.clientserver.nonthreaded; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import common.MessageUtils; public class Server implements Runnable { ServerSocket serverSocket; volatile boolean keepProcessing = true; public Server(int port, int millisecondsTimeout) throws IOException { serverSocket = new ServerSocket(port); serverSocket.setSoTimeout(millisecondsTimeout); } public void run() { System.out.printf("Server Starting\n"); while (keepProcessing) { try { System.out.printf("accepting client\n"); Socket socket = serverSocket.accept(); System.out.printf("got client\n"); process(socket); } catch (Exception e) { handle(e); } } } продолжение 385 386 Приложение А . Многопоточность II листинг А .3 (продолжение) private void handle(Exception e) { if (!(e instanceof SocketException)) { e.printStackTrace(); } } public void stopProcessing() { keepProcessing = false; closeIgnoringException(serverSocket); } void process(Socket socket) { if (socket == null) return; try { System.out.printf("Server: getting message\n"); String message = MessageUtils.getMessage(socket); System.out.printf("Server: got message: %s\n", message); Thread.sleep(1000); System.out.printf("Server: sending reply: %s\n", message); MessageUtils.sendMessage(socket, "Processed: " + message); System.out.printf("Server: sent\n"); closeIgnoringException(socket); } catch (Exception e) { e.printStackTrace(); } } private void closeIgnoringException(Socket socket) { if (socket != null) try { socket.close(); } catch (IOException ignore) { } } private void closeIgnoringException(ServerSocket serverSocket) { if (serverSocket != null) try { serverSocket.close(); } catch (IOException ignore) { } } } 386 org.jfree.date.SerialDate 387 листинг А .4 . ClientTest.java package com.objectmentor.clientserver.nonthreaded; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import common.MessageUtils; public class Server implements Runnable { ServerSocket serverSocket; volatile boolean keepProcessing = true; public Server(int port, int millisecondsTimeout) throws IOException { serverSocket = new ServerSocket(port); serverSocket.setSoTimeout(millisecondsTimeout); } public void run() { System.out.printf("Server Starting\n"); while (keepProcessing) { try { System.out.printf("accepting client\n"); Socket socket = serverSocket.accept(); System.out.printf("got client\n"); process(socket); } catch (Exception e) { handle(e); } } } private void handle(Exception e) { if (!(e instanceof SocketException)) { e.printStackTrace(); } } public void stopProcessing() { keepProcessing = false; closeIgnoringException(serverSocket); } void process(Socket socket) { if (socket == null) return; продолжение 387 388 Приложение Б . Многопоточность II листинг А .4 (продолжение) try { System.out.printf("Server: getting message\n"); String message = MessageUtils.getMessage(socket); System.out.printf("Server: got message: %s\n", message); Thread.sleep(1000); System.out.printf("Server: sending reply: %s\n", message); MessageUtils.sendMessage(socket, "Processed: " + message); System.out.printf("Server: sent\n"); closeIgnoringException(socket); } catch (Exception e) { e.printStackTrace(); } } private void closeIgnoringException(Socket socket) { if (socket != null) try { socket.close(); } catch (IOException ignore) { } } private void closeIgnoringException(ServerSocket serverSocket) { if (serverSocket != null) try { serverSocket.close(); } catch (IOException ignore) { } } } листинг А .5 . MessageUtils.java package common; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.net.Socket; public class MessageUtils { public static void sendMessage(Socket socket, String message) throws IOException { OutputStream stream = socket.getOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(stream); oos.writeUTF(message); oos.flush(); } 388 org.jfree.date.SerialDate 389 public static String getMessage(Socket socket) throws IOException { InputStream stream = socket.getInputStream(); ObjectInputStream ois = new ObjectInputStream(stream); return ois.readUTF(); } } Архитектура «клиент/сервер» с использованием потоков Перевод сервера на многопоточную архитектуру сводится к простому изменению функции process (новые строки выделены жирным шрифтом): void process(final Socket socket) { if (socket == null) return; Runnable clientHandler = new Runnable() { public void run() { try { System.out.printf("Server: getting message\n"); String message = MessageUtils.getMessage(socket); System.out.printf("Server: got message: %s\n", message); Thread.sleep(1000); System.out.printf("Server: sending reply: %s\n", message); MessageUtils.sendMessage(socket, "Processed: " + message); System.out.printf("Server: sent\n"); closeIgnoringException(socket); } catch (Exception e) { e.printStackTrace(); } } }; Thread clientConnection = new Thread(clientHandler); clientConnection.start(); } 389 org .jfree .date .SerialDate листинг Б .1 . SerialDate.Java 1 /* ======================================================================== 2 * JCommon : библиотека классов общего назначения для платформы Java(tm) 3 * ======================================================================== 4 * 5 * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors. 6 * 7 * Информация о проекте: http://www.jfree.org/jcommon/index.html 8 * 9 * Библиотека распространяется бесплатно; вы можете свободно распространять 10 * и/или изменять ее на условиях лицензии Lesser General Public License 11 * в формулировке Free Software Foundation; либо версии 2.1 лицензии, либо 12 * (на ваше усмотрение) любой последующей версии. 13 * 14 * Библиотека распространяется в надежде, что она будет полезна, но 15 * БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ, даже без подразумеваемой гарантии ПРИГОДНОСТИ 16 * для КОНКРЕТНОЙ ЦЕЛИ. За подробностями обращайтесь к GNU Lesser General 17 * Public License. 18 * 19 * Вы должны получить копию лицензии GNU Lesser General Public License 20 * с этой библиотекой; если этого не произошло, обратитесь в Free Software 21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 22 * USA. 23 * 24 * [Java является зарегистрированной торговой маркой Sun Microsystems, Inc. 25 * в Соединенных Штатах и других странах]. 26 * 27 * --------------- 28 * SerialDate.java 29 * --------------- 30 * (C) Copyright 2001-2005, by Object Refinery Limited. 31 * 32 * Автор: Дэвид Гилберт (для Object Refinery Limited); 33 * Участники: -; 34 * 35 * $Id: SerialDate.java,v 1.7 2005/11/03 09:25:17 mungady Exp $ Б 390 org.jfree.date.SerialDate 391 36 * 37 * Изменения (начиная с 11 октября 2001) 38 * -------------------------- 39 * 11.10.2001 : Реорганизация класса и его перемещение в новый пакет 40 * com.jrefinery.date (DG); 41 * 05.12.2001 : Добавление метода getDescription(), исключение класса 42 * NotableDate (DG); 43 * 12.12.2001 : После удаления класса NotableDate IBD требует наличия 44 * метода setDescription() (DG); исправлены ошибки 45 * в функциях getPreviousDayOfWeek(), getFollowingDayOfWeek() 46 * и getNearestDayOfWeek() (DG); 47 * 05.12.2001 : Исправление ошибки в классе SpreadsheetDate (DG); 48 * 29.05.2002 : Перемещение констант месяцев в отдельный интерфейс 49 * (MonthConstants) (DG); 50 * 27.08.2002 : Исправление ошибки в addMonths(), спасибо N???levka Petr (DG); 51 * 03.10.2002 : Исправление ошибок по информации Checkstyle (DG); 52 * 13.03.2003 : Реализация Serializable (DG); 53 * 29.05.2003 : Исправление ошибки в методе addMonths (DG); 54 * 04.09.2003 : Реализация Comparable. Обновление Javadoс для isInRange (DG); 55 * 05.01.2005 : Исправление ошибки в методе addYears() (1096282) (DG); 56 * 57 */ 58 59 package org.jfree.date; 60 61 import java.io.Serializable; 62 import java.text.DateFormatSymbols; 63 import java.text.SimpleDateFormat; 64 import java.util.Calendar; 65 import java.util.GregorianCalendar; 66 67 /** 68 * Абстрактный класс, определяющий требования для манипуляций с датами 69 * без привязки к конкретной реализации. 70 * 71 * Требование 1 : совпадение с представлением дат в формате Excel; 72 * Требование 2 : класс должен быть неизменным; 73 * 74 * Почему не использовать java.util.Date? Будем использовать, где это имеет смысл. 75 * Класс java.util.Date бывмает *слишком* точным - он представляет момент 76 * времени с точностью до 1/100 секунды (при этом сама дата зависит от часового 77 * пояса). Иногда бывает нужно просто представить конкретный день (скажем, 78 * 21 января 2015), не заботясь о времени суток, часовом поясе и т.д. 79 * Именно для таких ситуаций определяется класс SerialDate. 80 * 81 * Вы можете вызвать getInstance() для получения конкретного субкласса 82 * SerialDate, не беспокоясь о реализации. 83 * 84 * @author David Gilbert 85 */ продолжение 391 392 Приложение Б . org.jfree.date.SerialDate листинг Б .1 (продолжение) 86 public abstract class SerialDate implements Comparable, 87 Serializable, 88 MonthConstants { 89 90 /** Для сериализации. */ 91 private static final long serialVersionUID = -293716040467423637L; 92 93 /** Символические обозначения формата даты. */ 94 public static final DateFormatSymbols 95 DATE_FORMAT_SYMBOLS = new SimpleDateFormat().getDateFormatSymbols(); 96 97 /** Порядковый номер для 1 января 1900. */ 98 public static final int SERIAL_LOWER_BOUND = 2; 99 100 /** Порядковый номер для 31 декабря 9999. */ 101 public static final int SERIAL_UPPER_BOUND = 2958465; 102 103 /** Наименьшее значение года, поддерживаемое форматом даты. */ 104 public static final int MINIMUM_YEAR_SUPPORTED = 1900; 105 106 /** Наибольшее значение года, поддерживаемое форматом даты. */ 107 public static final int MAXIMUM_YEAR_SUPPORTED = 9999; 108 109 /** Константа для понедельника, эквивалент java.util.Calendar.MONDAY. */ 110 public static final int MONDAY = Calendar.MONDAY; 111 112 /** 113 * Константа для вторника, эквивалент java.util.Calendar.TUESDAY. 114 */ 115 public static final int TUESDAY = Calendar.TUESDAY; 116 117 /** 118 * Константа для среды, эквивалент 119 * java.util.Calendar.WEDNESDAY. 120 */ 121 public static final int WEDNESDAY = Calendar.WEDNESDAY; 122 123 /** 124 * Константа для четверга, эквивалент java.util.Calendar.THURSDAY. 125 */ 126 public static final int THURSDAY = Calendar.THURSDAY; 127 128 /** Константа для пятницы, эквивалент java.util.Calendar.FRIDAY. */ 129 public static final int FRIDAY = Calendar.FRIDAY; 130 131 /** 132 * Константа для субботы, эквивалент java.util.Calendar.SATURDAY. 133 */ 134 public static final int SATURDAY = Calendar.SATURDAY; 135 392 |