我们已经知道关于 线程同步 以及使用同步 关键字的各种机制。 Java 提供了另一种基于 Lock 接口和实现它的类(例如
ReentrantLock
).在本教程中,我们将看到 Lock
接口的基本用法,以解决打印机队列问题。
锁定界面
java.util.concurrent.locks.Lock
是一种线程同步机制,就像同步块一样。但是,Lock
比同步块更灵活、更复杂。由于 Lock
是一个接口,您需要使用其实现之一才能在您的应用程序中使用 Lock。 ReentrantLock
就是 Lock 接口的一种实现。
下面是Lock接口的简单使用。
Lock lock = new ReentrantLock();
lock.lock();
//critical section
lock.unlock();
首先创建一个锁。然后它的
lock()
方法被调用。现在 Lock
实例被锁定。任何其他调用 lock()
的线程都将被阻塞,直到锁定锁的线程调用 unlock()
。最后 unlock()
被调用,Lock
现在被解锁,其他线程可以锁定它。
Lock接口和synchronized关键字的区别
Lock 和 synchronized 块之间的主要区别是:
1) 尝试访问
synchronized
块时超时是不可能的。使用 Lock.tryLock(long timeout, TimeUnit timeUnit),是可以的。 2) synchronized 块必须完全包含在单个方法中。 Lock 可以在不同的方法中调用
lock()
和 unlock()
。
使用锁模拟打印机队列
在此示例中,程序将模拟打印机的行为。您可以在不同的时间间隔内或同时向打印机提交多个打印作业。打印机将从打印机队列中取出一个作业并打印它。其余的工作将在那里等待轮到他们。一旦打印机完成手头的打印作业,它将从队列中选择另一个作业并开始打印。保持这种循环发生。
PrintingJob.java
此类代表可以提交给打印机的独立打印。这个类实现了
Runnable
接口,这样打印机就可以执行它了。
class PrintingJob implements Runnable
{
private PrinterQueue printerQueue;
public PrintingJob(PrinterQueue printerQueue)
{
this.printerQueue = printerQueue;
}
@Override
public void run()
{
System.out.printf("%s: Going to print a document\n", Thread.currentThread().getName());
printerQueue.printJob(new Object());
}
}
打印机队列.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();
}
}
}
Output:
Thread 0: Going to print a document
Thread 9: Going to print a document
Thread 8: Going to print a document
Thread 7: Going to print a document
Thread 5: Going to print a document
Thread 6: Going to print a document
Thread 4: Going to print a document
Thread 3: Going to print a document
Thread 2: Going to print a document
Thread 1: Going to print a document
Thread 0: PrintQueue: Printing a Job during 8 seconds :: Time - Tue Jan 06 15:19:02 IST 2015
Thread 0: The document has been printed
Thread 9: PrintQueue: Printing a Job during 1 seconds :: Time - Tue Jan 06 15:19:11 IST 2015
Thread 9: The document has been printed
Thread 8: PrintQueue: Printing a Job during 8 seconds :: Time - Tue Jan 06 15:19:12 IST 2015
Thread 8: The document has been printed
Thread 7: PrintQueue: Printing a Job during 9 seconds :: Time - Tue Jan 06 15:19:21 IST 2015
Thread 7: The document has been printed
Thread 5: PrintQueue: Printing a Job during 7 seconds :: Time - Tue Jan 06 15:19:31 IST 2015
Thread 5: The document has been printed
Thread 6: PrintQueue: Printing a Job during 5 seconds :: Time - Tue Jan 06 15:19:39 IST 2015
Thread 6: The document has been printed
Thread 4: PrintQueue: Printing a Job during 2 seconds :: Time - Tue Jan 06 15:19:44 IST 2015
Thread 4: The document has been printed
Thread 3: PrintQueue: Printing a Job during 2 seconds :: Time - Tue Jan 06 15:19:46 IST 2015
Thread 3: The document has been printed
Thread 2: PrintQueue: Printing a Job during 5 seconds :: Time - Tue Jan 06 15:19:49 IST 2015
Thread 2: The document has been printed
Thread 1: PrintQueue: Printing a Job during 5 seconds :: Time - Tue Jan 06 15:19:54 IST 2015
Thread 1: The document has been printed
该示例的关键在于
PrinterQueue
类的 printJob()
方法。当我们想要使用锁来实现临界区并保证只有一个执行线程运行一段代码时,我们必须创建一个ReentrantLock
对象。在临界区的开始,我们必须使用lock()
方法来获得锁的控制权。
在临界区结束时,我们必须使用
unlock()
方法来释放锁的控制权,并允许其他线程运行这个临界区。 如果在临界区末尾不调用unlock()
方法,等待该块的其他线程将永远等待,导致死锁情况。 如果您在临界区使用 try-catch 块,请不要忘记将包含 unlock()
方法的语句放在 finally 区中。
阅读更多:如何在 java 中创建和解决死锁
您必须非常小心地使用 Locks 以避免死锁。当两个或多个线程被阻塞等待永远不会被解锁的锁时,就会发生这种情况。例如,一个线程(A)锁定一个Lock(X),一个线程(B)锁定一个Lock(Y)。如果现在,线程 (A) 尝试锁定 Lock (Y) 并且线程 (B) 同时尝试锁定 Lock (X),两个线程将被无限期阻塞,因为它们正在等待永远不会被释放的锁.请注意,出现问题是因为两个线程都试图以相反的顺序获取锁。
快乐学习!!
地址:https://www.cundage.com/article/how-to-use-locks-in-java-java-util-concurrent-locks-lock-tutorial-and-example.html