声明:本文是《 》的第七章,作者: Javier Fernández González 译者:许巧辉
实现一个自定义的Lock类
锁是Java并发API提供的基本同步机制之一。它允许程序员保护代码的临界区,所以,在某个时刻只有一个线程能执行这个代码块。它提供以下两种操作:
- lock():当你想要访问一个临界区时,调用这个方法。如果有其他线程正在运行这个临界区,其他线程将阻塞,直到它们被这个锁唤醒,从而获取这个临界区的访问。
- unlock():你在临界区的尾部调用这个方法,允许其他线程访问这个临界区。
在Java并发API中,锁是在Lock接口及其一些实现类中声明的,比如ReentrantLock类。
在这个指南中,你将学习如何实现你自己的Lock对象,它将实现一个实现了Lock接口并可用来保护临界区的类。
准备工作…
这个指南的例子使用Eclipse IDE实现。如果你使用Eclipse或其他IDE,如NetBeans,打开它并创建一个新的Java项目。
如何做…
按以下步骤来实现的这个例子:
1.创建一个继承AbstractQueuedSynchronizer类的MyQueuedSynchronizer类。
1 | public class MyAbstractQueuedSynchronizer extends |
2 | AbstractQueuedSynchronizer { |
2.声明一个私有的、AtomicInteger类型的属性state。
1 | private AtomicInteger state; |
3.实现这个类的构造器,并初始化它的属性。
1 | public MyAbstractQueuedSynchronizer() { |
2 | state= new AtomicInteger( 0 ); |
4.实现tryAcquire()方法。这个方法试图将变量state的值从0变成1。如果成功,它将返回true,否则,返回false。
2 | protected boolean tryAcquire( int arg) { |
3 | return state.compareAndSet( 0 , 1 ); |
5.实现tryRelease()方法。这个方法试图将变量sate的值从1变成0.如果成功,它将返回true,否则,返回false。
2 | protected boolean tryRelease( int arg) { |
3 | return state.compareAndSet( 1 , 0 ); |
6.创建一个MyLock类,并指定它实现Lock接口。
1 | public class MyLock implements Lock{ |
7.声明一个私有的、AbstractQueuedSynchronizer类型的属性sync。
1 | private AbstractQueuedSynchronizer sync; |
8.实现这个类的构造器,并使用MyAbstractQueueSynchronizer对象来初始化它的sync属性。
2 | sync= new MyAbstractQueuedSynchronizer(); |
9.实现lock()方法。调用sync对象的acquire()方法。
10.实现lockInterruptibly()方法。调用sync对象的acquireInterruptibly()方法。
2 | public void lockInterruptibly() throws InterruptedException { |
3 | sync.acquireInterruptibly( 1 ); |
11.实现tryLock()方法。调用sync对象的tryAcquireNanos()方法。
2 | public boolean tryLock() { |
4 | return sync.tryAcquireNanos( 1 , 1000 ); |
5 | } catch (InterruptedException e) { |
12.实现其他版本的带有两个参数的tryLock()方法。一个long类型参数,名为time,一个TimeUnit类型参数,名为unit。调用sync对象的tryAcquireNanos()方法。
2 | public boolean tryLock( long time, TimeUnit unit) |
3 | throws InterruptedException { |
4 | return sync.tryAcquireNanos( 1 , TimeUnit.NANOSECONDS. |
13.实现unlock()方法。调用sync对象的release()方法。
14.实现newCondition()方法。创建一个新的sync对象的内部类ConditionObject。
2 | public Condition newCondition() { |
3 | return sync. new ConditionObject(); |
15.创建一个Task类,并指定它实现Runnable接口。
1 | public class Task implements Runnable { |
16.声明一个私有的、MyLock类型的属性lock。
17.声明一个私有的、String类型的属性name。
18.实现这个类的构造器,并初始化它的属性。
1 | public Task(String name, MyLock lock){ |
19.实现这个类的run()方法。获取锁,令线程睡眠2秒,然后,释放这个lock对象。
04 | System.out.printf( "Task: %s: Take the lock\n" ,name); |
06 | TimeUnit.SECONDS.sleep( 2 ); |
07 | System.out.printf( "Task: %s: Free the lock\n" ,name); |
08 | } catch (InterruptedException e) { |
20.实现这个例子的主类,通过创建Main类,并实现main()方法。
2 | public static void main(String[] args) { |
21.创建一个MyLock对象,名为lock。
1 | MyLock lock= new MyLock(); |
22.创建和执行10个Task任务。
1 | for ( int i= 0 ; i< 10 ; i++){ |
2 | Task task= new Task( "Task-" +i,lock); |
3 | Thread thread= new Thread(task); |
23.使用tryLock()方法尝试获取锁。等待1秒,如果你没有获取锁,写入一条信息并重新尝试。
04 | value=lock.tryLock( 1 ,TimeUnit.SECONDS); |
06 | System.out.printf( "Main: Trying to get the Lock\n" ); |
08 | } catch (InterruptedException e) { |
24.写入一条信息表明你已获取锁,然后释放它。
1 | System.out.printf( "Main: Got the lock\n" ); |
25.写入一条信息表明程序的结束。
1 | System.out.printf( "Main: End of the program\n" ); |
它是如何工作的…
Java并发API提供一个类,可以用来实现拥有锁和信号量特征的同步机制。它就是AbstractQueuedSynchronizer,正如其名,它是一个抽象类。它提供控制临界区的访问和管理正在阻塞等待访问临界区的线程队列的操作。这些操作是基于以下两个抽象方法:
- tryAcquire():尝试访问临界区时,调用这个方法。如果线程调用这个方法可以访问临界区,那么这个方法返回true,否则,返回false。
- tryRelease():尝试翻译临界区的访问,调用这个方法。如果线程调用这个方法可以释放临界区的访问,那么这个方法返回true,否则,返回false.
在这些方法中,你已实现可用来控制临界区访问的机制。在你的例子中,你已实现继承AbstractQueuedSyncrhonizer类的MyQueuedSynchonizer类,并使用AtomicInteger变量实现抽象方法来控制临界区的访问。如果锁是自由的,这个变量的值为0,表明线程可以访问这个临界区。如果锁是阻塞的,这个变量的值为1,表明线程不能访问这个临界区。
你已使用AtomicInteger类提供的compareAndSet()方法,尝试将你指定的值作为第一个参数改变成你指定的值作为第二个参数。实现tryAcquire()方法,你尝试将原子变量的值从0变成1。同样地,你实现tryRelease()方法,尝试将原子变量的值从1变成0。
你必须实现这个类,因为AbstractQueuedSynchronizer类的其他实现(比如,所使用的ReentrantLock类)是作为私有的内部类使用来实现的,所以你不能访问它。
然后,你已实现MyLock类。这个类实现Lock接口,有一个MyQueuedSynchronizer对象属性。你已使用MyQueuedSynchronizer对象的方法,来实现Lock接口的所有方法。
最后,你实现了Task类,它实现了Runnable接口,并使用一个MyLock对象来控制临界区的访问。这个临界区令线程睡眠2秒。主类创建一个MyLock对象,并运行10个Task对象来共享这把锁。主类也使用tryLock()方法来尝试获取锁的访问。
当你执行这个例子,你可以看到只有一个线程可以访问这个临界区,并且当这个线程结束,其他线程可以继续访问这个临界区。
你可以使用你自己的锁来写入关于它的使用的日志信息,控制锁定时间,或实现先进的同步机制来控制。比如,只能在特定的时间内,才能对资源访问。
不止这些…
AbstractQueuedSynchronizer类提供两个方法可以用来控制锁的状态,它们就是getState()和setState()方法。这两个方法,接收和返回一个整数值作为锁的状态。你可以使用这两个方法而不是AtomicInteger属性来存储锁的状态。
Java并发API提供其他类来实现同步机制。它就是AbstractQueuedLongSynchronizer类,它与AbstractQueuedSynchronizer一样,除了使用一个long类型属性来存储线程的状态。
参见