Факультет Информационных Технологий, 2-й курс, II семестр Курс: Объектно-ориентированное программирование Семинар №6. Синхронизация. Многопоточность. План 1. Вопросы по заданиям семинара №5 2. Механизм синхронизации в Java. Нити выполнения, объекты и мониторы. 3. Захват и освобождение монитора, блок synchronized 4. Класс потока Thread, запуск нового потока 5. Метод выполнения run(), время жизни потока 6. Пользовательский и Daemon потоки 7. Ожидание и прерывание потока. Методы join и interrupt 8. Исключение InterruptedException 9. Текущий поток выполнения Thread.currentThread() 10. Остановка выполнения без захвата монитора в методе Thread.sleep() 11. Захват и временное освобождение монитора. Методы wait и notify класса Object 12. Блокирование потока DeadLock. Методы stop, suspend, resume и destroy 13. Интерфейс Runnable в качестве альтернативы наследования класса Thread Пример запуска потока public class RunThread { public static void main(String[] args) { System.out.println("Start"); Thread t = new Thread("Test-Thread") { public void run() { System.out.println("Run and sleep " + getName()); try { sleep(3000); } catch (InterruptedException e) { System.out.println("Interrupted"); } } }; t.start(); try { t.join(); } catch (InterruptedException e) { } System.out.println("Finish"); } } Пример не синхронизированного доступа public class Increment extends Thread { private static int NUM_THREADS = 3; private static int NUM_INCREASES = 1000000; private static long data; //private static Object obj = new Object(); public Increment(String name) { super(name); } public void run() { System.out.println("Thread "+Thread.currentThread().getName()+" started"); 1 Факультет Информационных Технологий, 2-й курс, II семестр Курс: Объектно-ориентированное программирование for (int i=0; i<NUM_INCREASES; i++) { //synchronized (obj) { data++; //} } System.out.println("Thread "+Thread.currentThread().getName()+" stopped"); } public static void main(String[] args) { Increment threads[] = new Increment[NUM_THREADS]; for(int i=0; i<NUM_THREADS; i++) { threads[i] = new Increment("#"+i); threads[i].start(); } for(int i=0; i<NUM_THREADS; i++) { try { threads[i].join(); } catch (InterruptedException e) {} } System.out.println("Expected: "+(NUM_THREADS*NUM_INCREASES)+" but it is: "+data); } } Примеры синхронизированного доступа public static synchronized void syncIncMethod() { data++; } public static void syncIncBlock() { synchronized(Increment.class) { data++; } } Захват монитора объекта synchronized (obj) { // захват монитора объекта obj try { obj.wait(); // поток освобождает монитор и застывает в этой точке // эта сторока выполнится только после того, как другой поток скажет obj.notify() } catch (InterruptedException e) { // если поток разбудили через метод interrupt(); } } synchronized (obj) { // захват монитора объекта obj obj.notify(); // посылка сообщения потоку, который первый вызвал obj.wait() и все еще ждет } synchronized (obj) { // захват монитора объекта obj obj.notifyAll(); // посылка сообщения всем потокам, которые висят в obj.wait() 2 Факультет Информационных Технологий, 2-й курс, II семестр Курс: Объектно-ориентированное программирование } Пример deadlock public class Deadlock { private Run1 run1; private Run2 run2; public class Run1 implements Runnable { public void run() { f1(); } public synchronized void f1() { try { Thread.sleep(1000); } catch (InterruptedException e){ return; } System.out.println("Run1.f1()"); run2.f2(); } public synchronized void f2() { System.out.println("Run1.f2()"); } } public class Run2 implements Runnable { public void run() { f1(); } public synchronized void f1() { try { Thread.sleep(1000); } catch (InterruptedException e){ return; } System.out.println("Run2.f1()"); run1.f2(); } public synchronized void f2() { System.out.println("Run2.f2()"); } } private void init() { run1 = new Run1(); new Thread(run1).start(); run2 = new Run2(); new Thread(run2).start(); } public static void main(String args[]) { new Deadlock().init(); } } 3 Факультет Информационных Технологий, 2-й курс, II семестр Курс: Объектно-ориентированное программирование Задание 6-1: Секундомер Написать секундомер - класс Stopwatch - для замера времени в отдельном потоке выполнения. В классе должны быть реализованы следующие методы start – начинает отчет времени stop – прерывает отчет времени reset – сбрасывает текущее значение секундомера getTime – возвращает отсчитанное время в миллисекундах Для демонстрации работы секундомера написать консольное приложение. Пользователю должны быть доступными следующие команды: start N – запустить секундомер и дать ему идентификатор N stop N – остановить секундомер с идентификатором N reset N – сбросить время у секундомера с идентификатором N time N – показать время у секундомера с идентификатором N help – список команд exit – выход Если не указан идентификатор, то используется 0 как идентификатор по умолчанию. Хранить секундомеры лучше в Map<String, Stopwatch>. Задание 6-2: Подсчет простых чисел Написать Swing-приложение для подсчета простых чисел в параллельных потоках. Каждый поток подсчитывает простые числа из заданного диапазона. Всего должно быть запущено три потока подсчета. Полученные числа выдаются на экран. Можно запускать/останавливать подсчет несколько раз. Пользователю доступны следующие элементы управления: 1. Кнопка запуска всех потоков 2. Кнопка остановки всех потоков 3. Поле (JTextArea) для вывода результата в виде: <номер потока>: <число> Например: 1: 11 2: 23 2: 31 1: 17 4. 4 поля ввода диапазонов чисел для подсчета. Получается 3 диапазона (между 4-мя числами) для каждого потока. 5. Три строки с информацией о состоянии каждого потока 4