Java предоставляет другой механизм для синхронизации блоков кода на основе интерфейса Lock и классов, которые его реализуют(например, ReentrantLock ). В этом уроке мы рассмотрим базовое использование интерфейса Lock для решения проблемы очереди принтера.
1. Интерфейс блокировки
Java.util.concurrent.locks.Lock — это механизм синхронизации потоков, как и синхронизированные блоки. Однако Lock более гибкий и сложный, чем синхронизированный блок. Поскольку Lock — это интерфейс, вам нужно использовать одну из его реализаций, чтобы использовать Lock в своих приложениях. ReentrantLock — одна из таких реализаций интерфейса Lock.
Вот простое использование интерфейса Lock.
Lock lock = new ReentrantLock();lock.lock();//critical sectionlock.unlock();
Сначала создается Lock. Затем вызывается его метод lock(). Теперь экземпляр Lock заблокирован. Любой другой поток, вызывающий lock(), будет заблокирован, пока поток, заблокировавший lock, не вызовет unlock(). Наконец, вызывается unlock(), и Lock теперь разблокирован, так что другие потоки могут его заблокировать.
2. Разница между Lock и Synchronized Keyword
Основные различия между блокировкой и синхронизированным блоком:
- Невозможно получить доступ к синхронизированному блоку с таймаутом. Используя Lock.tryLock(long timeout, TimeUnit timeUnit), это возможно.
- Синхронизированный блок должен полностью содержаться в одном методе. Блокировка может иметь вызовы lock() и unlock() в отдельных методах.
3. Имитация очереди печати с использованием блокировок
В этом примере программа будет имитировать поведение принтера. Вы можете отправить несколько заданий на печать на принтер в течение различных интервалов времени или одновременно. Принтер возьмет задание из очереди принтера и напечатает его. Остальные задания будут ждать своей очереди. Как только принтер закончит с заданием на печать, он выберет другое задание из очереди и начнет печатать. Зацикливайте это.
PrintingJob.java
Этот класс представляет независимую печать, которая может быть отправлена на принтер. Этот класс реализует интерфейс Runnable, так что принтер может выполнить ее, когда придет его очередь.
class PrintingJob implements Runnable{private PrinterQueue printerQueue;public PrintingJob(PrinterQueue printerQueue){this.printerQueue = printerQueue;}@Overridepublic void run(){System.out.printf("%s: Going to print a document\n", Thread.currentThread().getName());printerQueue.printJob(new Object());}}
PrinterQueue.java
Этот класс представляет очередь принтера/принтер. Блокировка поддерживается принтером для запуска нового задания печати сразу после завершения текущего задания печати.
class PrinterQueue{private final Lock queueLock = new ReentrantLock();public void printJob(Object document){queueLock.lock();try{Long duration =(long)(Math.random() * 10000);System.out.println(Thread.currentThread().getName() + ": PrintQueue: Printing a Job during " +(duration / 1000) + " seconds :: Time - " + new Date());Thread.sleep(duration);} catch(InterruptedException e){e.printStackTrace();} finally{System.out.printf("%s: The document has been printed\n", Thread.currentThread().getName());queueLock.unlock();}}}
Давайте проверим нашу программу принтера:
public class LockExample{public static void main(String[] args){PrinterQueue printerQueue = new PrinterQueue();Thread thread[] = new Thread[10];for(int i = 0; i < 10; i++){thread[i] = new Thread(new PrintingJob(printerQueue), "Thread " + i);}for(int i = 0; i < 10; i++){thread[i].start();}}}
Вывод программы:
Thread 0: Going to print a documentThread 9: Going to print a documentThread 8: Going to print a documentThread 7: Going to print a documentThread 5: Going to print a documentThread 6: Going to print a documentThread 4: Going to print a documentThread 3: Going to print a documentThread 2: Going to print a documentThread 1: Going to print a documentThread 0: PrintQueue: Printing a Job during 8 seconds :: Time - Tue Jan 06 15:19:02 IST 2015Thread 0: The document has been printedThread 9: PrintQueue: Printing a Job during 1 seconds :: Time - Tue Jan 06 15:19:11 IST 2015Thread 9: The document has been printedThread 8: PrintQueue: Printing a Job during 8 seconds :: Time - Tue Jan 06 15:19:12 IST 2015Thread 8: The document has been printedThread 7: PrintQueue: Printing a Job during 9 seconds :: Time - Tue Jan 06 15:19:21 IST 2015Thread 7: The document has been printedThread 5: PrintQueue: Printing a Job during 7 seconds :: Time - Tue Jan 06 15:19:31 IST 2015Thread 5: The document has been printedThread 6: PrintQueue: Printing a Job during 5 seconds :: Time - Tue Jan 06 15:19:39 IST 2015Thread 6: The document has been printedThread 4: PrintQueue: Printing a Job during 2 seconds :: Time - Tue Jan 06 15:19:44 IST 2015Thread 4: The document has been printedThread 3: PrintQueue: Printing a Job during 2 seconds :: Time - Tue Jan 06 15:19:46 IST 2015Thread 3: The document has been printedThread 2: PrintQueue: Printing a Job during 5 seconds :: Time - Tue Jan 06 15:19:49 IST 2015Thread 2: The document has been printedThread 1: PrintQueue: Printing a Job during 5 seconds :: Time - Tue Jan 06 15:19:54 IST 2015Thread 1: The document has been printed
Ключ к примеру находится в методе printJob() класса PrinterQueue. Когда мы хотим реализовать критическую секцию с использованием блокировок и гарантировать, что только один поток выполнения запускает блок кода, мы должны создать объект ReentrantLock. В начале критической секции мы должны получить контроль над блокировкой с помощью метода lock().
В конце критической секции мы должны использовать метод unlock(), чтобы освободить контроль над блокировкой и позволить другим потокам выполнить эту критическую секцию. Если вы не вызовете метод unlock() в конце критической секции, другие потоки, ожидающие этот блок, будут ждать вечно, что приведет к ситуации взаимоблокировки. Если вы используете блоки try-catch в своей критической секции, не забудьте поместить предложение, содержащее метод unlock(), внутрь раздела finally.
Смотрите также: Как создать взаимоблокировку и решить ее в Java
Вы должны быть очень осторожны с использованием Locks, чтобы избежать взаимоблокировок. Такая ситуация возникает, когда два или более потоков блокируются, ожидая блокировок, которые никогда не будут разблокированы. Например, поток(A) блокирует Lock(X), а поток(B) блокирует Lock(Y).
Если теперь поток(A) попытается заблокировать Lock(Y), а поток(B) одновременно попытается заблокировать Lock(X), оба потока будут заблокированы на неопределенный срок, поскольку они ждут блокировок, которые никогда не будут освобождены. Обратите внимание, что проблема возникает, поскольку оба потока пытаются получить блокировки в противоположном порядке.