Допустим есть такой код. Его результат:[Синхронизация]
[в Java]
[ полезная] . Если объект Caller запускать без отдельного потока (т.е без "extends Thread" и без метода "start()"), то результат будет в другом порядке- [Синхронизация] [полезная] [в Java] Почему так происходит? Прошу дать развернутый ответ.
class CallMe{
void call(String msg){
System.out.print("[" + msg );
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("]");
}
}
class Caller extends Thread{
String msg;
CallMe target;
Caller(CallMe target, String msg){
this.target = target;
this.msg = msg;
start();
}
public void run(){
synchronized (target) {
target.call(msg);
}
}
}
public class Main {
public static void main(String[] args) {
CallMe callMe = new CallMe();
new Caller(callMe, "Синхронизация");
new Caller(callMe, "в Java");
new Caller(callMe, " полезная");
}
}
Ответ
Поставьте задержку на 200 миллисекунд в методе run(), а в методе main() добавьте цикл операций на 100 - получите еще больше вариантов.
Потому что невозможно предсказать какой поток войдет в блок synchronized первым, даже если вы их запускаете последовательно.
Для синхронизации потоков используются классы
Semaphore
CountDownLatch
CyclicBarrier
Lock
у них различные цели и методы. Это зависит от конкретной задачи.
Semaphore - как правило служит для ограничения количества потоков при работе с ресурсами. Доступ ограничивается с помощью счетчика, если его значение больше нуля, то доступ потоку разрешается, а значение счетчика уменьшается. Если счетчик равен нулю, то текущий поток блокируется, пока другой поток не освободит ресурс. Для получения доступа используется метод acquire(), для освобождения – release()
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore smp = new Semaphore(2);
for (int i = 0; i < 5; i++) {
final int w = i;
new Thread(() -> {
try {
System.out.println("Поток" + w + " перед семафором");
smp.acquire();
System.out.println("Поток" + w + " получил доступ к ресурсу");
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("Поток" + w + " освободил ресурс");
smp.release();
}
}).start();
}
}
}
Результат работы:
Поток 0 перед семафором
Поток 0 получил доступ к ресурсу
Поток 2 перед семафором
Поток 2 получил доступ к ресурсу
Поток 1 перед семафором
Поток 4 перед семафором
Поток 3 перед семафором
Поток 2 освободил ресурс
Поток 1 получил доступ к ресурсу
Поток 0 освободил ресурс
Поток 4 получил доступ к ресурсу
Поток 4 освободил ресурс
Поток 1 освободил ресурс
Поток 3 получил доступ к ресурсу
Поток 3 освободил ресурс
Одновременно семафор могут захватить(с помощью метода acquire()) только два потока, остальные потоки становятся в очередь, пока один из потоков не освободит семафор методом release()
CountDownLatch - Позволяет потоку ожидать до тех пор, пока не завершится определенное количество операций, выполняющихся в других потоках, в режим ожидания поток заходит с помощью метода await(). Количество требуемых операций задается при создании объекта, после чего уменьшается при вызове метода countDown(). Как только счетчик доходит до 0, ожидающий поток разблокируется.
public class SimpleCDL {
public static void main(String[] args) {
// задаем кол-во потоков
final int THREADS_COUNT = 6;
// задаем значение счетчика
final CountDownLatch cdl = new CountDownLatch(THREADS_COUNT);
System.out.println("Начинаем");
for (int i = 0; i < THREADS_COUNT; i++) {
final int w = i;
new Thread(() -> {
try {
// считаем что выполнение задачи занимает ~1 сек
Thread.sleep(500 + (int)(500 * Math.random()));
// как только задача выполнена, уменьшаем счетчик
cdl.countDown();
System.out.println("Поток #" + w + " - готов");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
try {
// ждем пока счетчик не сбросится в ноль, пока это не
// произойдет, будем стоять на этой строке
cdl.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// как только все потоки выполнили свои задачи - пишем сообщение
System.out.println("Работа завершена");
}
}
Результат работы:
Начинаем
Поток #1 - готов
Поток #0 - готов
Поток #3 - готов
Поток #2 - готов
Поток #4 - готов
Поток #5 - готов
Работа завершена
Основной поток создает 6 потоков и ждет пока каждый из этих потоков закончит приготовление к работе.
CyclicBarrier - используется для синхронизации заданного количества потоков в одной точке. При вызове метода await() поток блокируется. Как только заданное количество потоков заблокировалось, с них одновременно снимается блокировка.
public class BarrierExample {
public static void main(String[] args) {
CyclicBarrier cb = new CyclicBarrier(3);
for (int i = 0; i < 3; i++) {
final int w = i;
new Thread(() -> {
try {
System.out.println("Поток " + w + " готовится");
Thread.sleep(100 + (int) (3000 * Math.random()));
System.out.println("Поток " + w + " готов");
cb.await();
System.out.println("Поток " + w + " запустился");
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
Результат работы:
Поток 0 готовится
Поток 1 готовится
Поток 2 готовится
Поток 2 готов
Поток 0 готов
Поток 1 готов
Поток 1 запустился
Поток 2 запустился
Поток 0 запустился
Результат работы:
Поток 0 готовится
Поток 1 готовится
Поток 2 готовится
Поток 2 готов
Поток 0 готов
Поток 1 готов
Поток 1 запустился
Поток 2 запустился
Поток 0 запустился
Несмотря на то, что какие-то потоки закончили подготовку раньше, какие-то позже, стартовали они в одно и то же время, так как блокировка снимается одновременно. Несмотря на то, что какие-то потоки закончили подготовку раньше, какие-то позже, стартовали они в одно и то же время, так как блокировка снимается одновременно.
Lock - Интерфейс. Представляет собой продвинутый механизм синхронизации потоков, который предоставляет большую гибкость чем блоки синхронизации. Поскольку Lock это интерфейс, для работы с ним необходимо создать объект одной из его реализаций.
Lock lock = new ReentrantLock();
lock.lock();
lock.unlock();
В начале создается объект типа Lock, после чего у этого объекта вызывается метод lock() и он захватывается. Попытка другого потока вызвать у этого же объекта метод lock() приведет к блокировке этого потока, пока поток удерживающий объект lock не освободит его с помощью метода unlock(). После вызова метода unlock() объект типа Lock освобождается и другие потоки могут его захватить
Основные отличия между Lock и синхронизированными блоками:
Синхронизированные блоки не гарантируют сохранность порядка обращения потоков к критической секции;
Выйти из синхронизированного блока по времени ожидания(timeout) не получится;
Синхронизированные блоки должны полностью содержаться в одном методе, в то время как Lock может быть захвачен в одном метода, а освобожден в другом.
Комментариев нет:
Отправить комментарий