
本文共 4976 字,大约阅读时间需要 16 分钟。
Java 并发编程 - Lock 锁机制
locks
Lock
是一个用于提供更加灵活的锁体验的机制。它允许开发者根据需要定义锁的行为,这与传统的 monopoly lock 有所不同。传统锁机制通常会阻塞所有试图访问共享资源的线程,直到持有锁的线程释放它。而Lock则提供了更细粒度的控制,同时允许线程在不影响其他线程的情况下以非阻塞方式访问共享资源。
传统同步机制的局限性
在传统的 Java 同步机制中,每个对象都有一个 intrinsic lock( Monitor ),这个 lock 是解耦的,它提供了绝对的同步能力。然而,这种机制的缺点在于它的使用方式比较粗略。在多线程环境中,任何一段使用 synchronized
修饰的代码都会阻塞所有试图进入该代码块的线程。这使得在某些场景下,例如读写锁、共享资源等情况下,使用传统的同步机制会显得力不从心。
##_lock的优势
相比于传统的 synchronizeLock, Lock 提供了更加灵活的锁机制。它允许开发者定义自己的锁的行为,这意味着我们可以根据具体的需求来配置锁。例如,某些情况下我们可能需要一个可重入锁,而另一些情况下可能需要非阻塞的锁。
lock 的基本使用方式
在使用 lock 的时候,最基本的流程是:
这种方式保证了锁的使用是显式的,也使得即使出现异常情况,锁也能正确地被释放。传统的 synchronize 是隐式的,这意味着一旦出现异常,代码块所在的 monitor 会被自动释放。
使用 lock 的示例
假设我们需要设计一个售票系统,其中多个线程可以同时购买票,而每次购买操作需要独占性的锁保护。以下是一个可能的实现方案:
import java.util.concurrent.Lock;import java.util.concurrent.ReentrantLock;public class Ticket { private static final int DEFAULT_TICKET_COUNT = 1000; private int count = DEFAULT_TICKET_COUNT; private int boughtCount = 0; Lock lock = new ReentrantLock(); public boolean buyTicket(int count) throws InterruptedException { if (count > this.count) { Thread.sleep(10); return false; } else { this.count -= count; Thread.sleep(1); boughtCount += count; return true; } }}class TicketRunnable implements Runnable { private Ticket ticket; private Lock lock; public TicketRunnable(Ticket ticket, Lock lock) { this.ticket = ticket; this.lock = lock; } @Override public void run() { for (int i = 0; i < 5; i++) { lock.lock(); try { int count = (int) Math.random() * 10 + 1; boolean success = ticket.buyTicket(count); System.out.println(Thread.currentThread().getName() + "打算买" + count + "张票,买票" + (success ? "成功" : "失败") + ",还剩下" + ticket.getCount() + "张票,总共卖掉" + ticket.getBuyedCount() + "张票,总票数" + (ticket.getCount() + ticket.getBuyedCount())); if (!success) { break; } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } }}public class SyncThreadTest { public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 20; i++) { new Thread(new TicketRunnable(new Ticket(), lock)).start(); } }}ReentrantLock lock = new ReentrantLock();
tryLock 方法
除了基本的 lock()
方法,Lock
还提供了 tryLock()
方法,这允许线程在一定的时间内试图获取锁。如果锁不可用,不会阻塞,而是返回一个布尔值。
使用 tryLock()
的好处在于我们可以在不影响其他线程的情况下,按照一定的策略进行锁的获取。例如,可以设置一个最长等待时间,超过该时间后退出并进行其他操作。
以下是一个使用 tryLock()
方法的例子:
import java.util.concurrent.Lock;public class TicketRunnable implements Runnable { private Ticket ticket; private Lock lock; public TicketRunnable(Ticket ticket, Lock lock) { this.ticket = ticket; this.lock = lock; } @Override public void run() { for (int i = 0; i < 5; ) { if (lock.tryLock()) { try { int count = (int) Math.random() * 10 + 1; boolean success = ticket.buyTicket(count); System.out.println(Thread.currentThread().getName() + "打算买" + count + "张票,买票" + (success ? "成功" : "失败") + ",还剩下" + ticket.getCount() + "张票,总共卖掉" + ticket.getBuyedCount() + "张票,总票数" + (ticket.getCount() + ticket.getBuyedCount())); if (!success) { break; } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); i++; } } else { System.out.println(Thread.currentThread().getName() + " 买票系统被占用,尝试获取锁失败."); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } }}
原理
Lock
的核心思想是允许更多灵活性地控制线程对共享资源的访问。具体来说,Lock
提供了几种方法:
lock()
相同,但保证能被打断。通过这些方法,开发者可以灵活地控制线程对共享资源的访问,既可以采用阻塞方式,也可以采用非阻塞策略。
总结
在实际应用中,Lock
比传统的 synchronized
提供了更大的灵活性。它允许开发者根据特定需求定义锁的行为,适用于更复杂的多线程场景。通过合理使用锁,可以显著提升多线程程序的性能和安全性。
如果觉得这篇文章很有用,欢迎在支付宝或微信打赏支持一下!
发表评论
最新留言
关于作者
