锁是Java并发API提供的基本同步机制之一,每次只有一个线程可以执行代码块,因此用来保护代码的关键部分。锁机制提供如下两种操作:
- lock():当访问临界区时调用此操作,如果线程正在运行此临界区,其它线程将被阻塞直到锁得到临界区访问权限时才被唤醒。
- unlock():在临界区结尾调用此方法,允许其它线程访问临界区。
在Java并发API中,锁在Lock接口中声明,且在一些类中实现,例如ReentrantLock类。
本节将通过实现Lock接口的类学习如何实现自定义Lock对象,用来保护临界区。
准备工作
本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。
实现过程
通过如下步骤实现范例:
-
创建名为MyAbstractQueuedSynchronizer的类,继承AbstractQueuedSynchronizer类:
public class MyAbstractQueuedSynchronizer extends AbstractQueuedSynchronizer{
-
声明名为state的私有AtomicInteger属性:
private final AtomicInteger state;
-
实现类构造函数,初始化属性:
public MyAbstractQueuedSynchronizer() { state=new AtomicInteger(0); }
-
实现tryAcquire()方法,此方法试图将状态变量值从0变成1。如果改变则返回true值,否则返回false:
扫描二维码关注公众号,回复: 4311522 查看本文章@Override protected boolean tryAcquire(int arg) { return state.compareAndSet(0, 1); }
-
实现tryRelease()方法,此方法试图将状态变量值从1变成0。如果改变则返回true值,否则返回false:
@Override protected boolean tryRelease(int arg) { return state.compareAndSet(1, 0); } }
-
创建名为MyLock的类,指定其实现Lock接口:
public class MyLock implements Lock {
-
声明名为sync的私有AbstractQueuedSynchronizer属性:
private final MyAbstractQueuedSynchronizer sync;
-
实现类构造函数,用新的MyAbstractQueueSynchronizer对象初始化sync属性:
public MyLock() { sync=new MyAbstractQueuedSynchronizer(); }
-
实现lock()方法,调用sync对象的acquire()方法:
@Override public void lock() { sync.acquire(1); }
-
实现lockInterruptibly()方法,调用sync对象的acquireInterruptibly()方法:
@Override public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1); }
-
实现tryLock()方法,调用sync对象的tryAcquireNanos()方法:
@Override public boolean tryLock() { try { return sync.tryAcquireNanos(1, 1000); } catch (InterruptedException e) { e.printStackTrace(); Thread.currentThread().interrupt(); return false; } }
-
实现tryLock()方法的另一个版本,包含两个参数:名为time的长整型参数和unit的TimeUnit参数。调用sync对象的tryAcquireNanos()方法:
@Override public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1, TimeUnit.NANOSECONDS.convert(time, unit)); }
-
实现unlock()方法, 调用sync对象的release()方法:
@Override public void unlock() { sync.release(1); }
-
实现newCondition()方法,创建sync对象内部类的新对象,名为ConditionObject:
@Override public Condition newCondition() { return sync.new ConditionObject(); } }
-
创建名为Task的类,指定其实现Runnable接口:
public class Task implements Runnable{
-
声明名为lock的私有MyLock属性:
private final MyLock lock;
-
声明名为name的私有String属性:
private final String name;
-
实现类构造函数,初始化属性:
public Task(String name, MyLock lock){ this.lock=lock; this.name=name; }
-
实现类的run()方法,获取锁,设置线程休眠2秒钟,然后释放锁对象:
@Override public void run() { lock.lock(); System.out.printf("Task: %s: Take the lock\n",name); try { TimeUnit.SECONDS.sleep(2); System.out.printf("Task: %s: Free the lock\n",name); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }
-
通过创建名为Main的类,添加main()方法,实现本范例主类:
public class Main { public static void main(String[] args) {
-
创建名为lock的MyLock对象:
MyLock lock=new MyLock();
-
创建和执行10个Task任务:
for (int i=0; i<10; i++){ Task task=new Task("Task-"+i,lock); Thread thread=new Thread(task); thread.start(); }
-
使用tryLock()方法试图得到锁。等待1秒钟,如果没有得到锁,输出信息到控制台,然后再次试图:
boolean value; do { try { value=lock.tryLock(1,TimeUnit.SECONDS); if (!value) { System.out.printf("Main: Trying to get the Lock\n"); } } catch (InterruptedException e) { e.printStackTrace(); value=false; } } while (!value);
-
输出指明得到锁且释放锁的信息到控制台:
System.out.printf("Main: Got the lock\n"); lock.unlock();
-
输出指明程序结束的信息到控制台:
System.out.printf("Main: End of the program\n"); } }
工作原理
Java并发API提供用来实现具有锁或信号的同步机制的类,称之为AbstractQueuedSynchronizer,从类名可以看出,这是一个抽象类。它提供控制对临界区访问的操作,并管理阻塞的线程队列,等待对临界区的访问。这些操作基于两个抽象方法:
- tryAcquire():在试图访问临界区时调用此方法,如果调用此方法的线程能够访问临界区,它将返回true值,否则返回false。
- tryRelease():在试图解除访问临界区时调用此方法,如果调用此方法的线程能够解除访问,它将返回true值,否则返回false。
在这些方法中,需要实现用来控制访问临界区的机制。本范例中,实现了MyAbstractQueuedSynchonizer类,此类继承AbstractQueuedSyncrhonizer类,并且使用AtomicInteger变量实现抽象方法来控制访问临界区。如果锁是空闲的,这个变量存储值为0,,因此线程能够访问临界区。如果锁是阻塞的话,则值为1,线程无法访问临界区。
- 还用到AtomicInteger类提供的compareAndSet()方法,此方法试图更改指定为第一个参数的值,并将值指定为第二个参数。为了实现tryAcquire()方法,试图将原子变量值从零变成一。同样地,为了实现tryRelease()方法,试图将原子变量值从一变成零。
因为AbstractQueuedSynchronizer类的其它实现(例如,通过ReentrantLock使用的实现)作为私有类在内部实现,所以必须实现AtomicInteger类。这是在使用它的类中执行的,所以无权访问它。
然后实现了MyLock类,此类实现了Lock接口且具有作为属性的MyQueuedSynchronizer对象。为了实现Lock接口的所有方法,使用了MyQueuedSynchronizer对象的方法。
最后,实现了Task类,此类实现了Runnable接口,且使用MyLock对象获得临界区访问权,此临界区设置线程休眠2秒钟。main类创建了MyLock对象,然后运行10个共享此锁的Task对象。main类还使用tryLock()方法试图获得锁访问权。
当执行本范例时,能过观察到如何只有一个线程能够访问临界区,并且当线程结束执行时,下一个线程接着访问临界区。
也能够使用自定义的Lock接口输出接口使用情况的日志信息,控制接口锁定的时间,或者实现高级的同步机制进行控制,例如访问资源,使其只能在特定时间可用。
扩展学习
AbstractQueuedSynchronizer类提供了两个方法,能够用来管理锁的状态,分别是getState()和setState()方法。这些方法接收和返回包含锁状态的整型值,可以使用它们代替AtomicInteger属性来存储锁状态。
AbstractQueuedLongSynchronizer类是Java并发API提供的另一个实现同步机制的类,与AbstractQueuedSynchronizer类相同,但它使用long属性存储线程状态。
更多关注
- 第二章“基础线程同步”中的“锁同步代码块”小节