实现一个定制锁

锁是 Java Concurrency API 提供的基本同步机制之一。它使得程序员可以包含一段代码临界区,同时只有一个线程能执行那段代码。 它提供了以下2个操作:

  • lock(): 想访问一个临界区时调用此方法。 如果有一个线程在运行此临界区,其他要执行此临界区的线程将被阻塞直到它们被锁唤醒以获得对临界区的权限。
  • unlock(): 在临界区末尾调用此方法以允许其他的线程访问此临界区。

在 Java Concurrency API 中,锁在 Lock 接口中被声明并在其他类(如 ReentrantLock 类)中被实现。

在本节中,将通过实现一个实现了能被用于包含一个临界区的 Lock 接口的类,来学习如何实现您自己的 Lock 对象。

实现

本节的示例代码在 com.elanzone.books.noteeg.chpt7.sect09 package中

  • MyQueuedSynchronizer 类 : extends AbstractQueuedSynchronizer

    • 属性 state : AtomicInteger 对象
    • 在构造函数中将 state 设置为0
            private AtomicInteger state;
    
            public MyQueuedSynchronizer() {
                state = new AtomicInteger(0);
            }
    
    • 覆盖实现 tryAcquire() 方法: 尝试将 state 的值从0改成1。如果可改,则返回 true;否则返回 false。
            @Override
            protected boolean tryAcquire(int arg) {
                return state.compareAndSet(0, 1);
            }
    
    • 覆盖实现 tryRelease() 方法: 尝试将 state 的值从1改成0。如果可改,则返回 true;否则返回 false。
            @Override
            protected boolean tryRelease(int arg) {
                return state.compareAndSet(1, 0);
            }
    
  • MyLock 类 : implements Lock

    • 属性 sync : AbstractQueuedSynchronizer 对象
    • 在构造函数中将 sync 设置为新 MyQueuedSynchronizer 对象
            private AbstractQueuedSynchronizer sync;
    
            public MyLock() {
                sync = new MyQueuedSynchronizer();
            }
    
    • 实现 Lock 接口的各方法,在各方法中调用 sync 的相应方法
            @Override
            public void lock() {
                sync.acquire(1);
            }
    
            @Override
            public void lockInterruptibly() throws InterruptedException {
                sync.acquireInterruptibly(1);
            }
    
            @Override
            public boolean tryLock() {
                try {
                    return sync.tryAcquireNanos(1, 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    return false;
                }
            }
    
            @Override
            public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
                return sync.tryAcquireNanos(1, TimeUnit.NANOSECONDS.convert(time, unit));
            }
    
            @Override
            public void unlock() {
                sync.release(1);
            }
    
            @Override
            public Condition newCondition() {
                return sync.new ConditionObject();
            }
    
  • 任务类 : Task : implements Runnable

    • 属性 lock : MyLock 对象
    • 属性 name : String 对象 :任务名称
    • 在构造函数中初始化各属性为参数值
            private MyLock lock;
            private String name;
    
            public Task(String name, MyLock lock) {
                this.name = name;
                this.lock = lock;
            }
    
    • 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

    1. 创建一个 MyLock 对象 lock
    2. 创建并执行 10 个 Task 任务
    3. 使用 tryLock() 方法尝试获得锁。如果不能获得则等上1秒钟后再试。
    4. 输出获得锁的信息后释放锁,再输出结束信息
            public static void main(String[] args) {
                MyLock lock = new MyLock();
    
                for (int i = 0; i < 10; i++) {
                    Task task = new Task("Task-" + i, lock);
                    Thread thread = new Thread(task);
                    thread.start();
                }
    
                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 Concurrency API 提供的 AbstractQueuedSynchronizer 类能被用于实现有锁或信号灯特性的同步机制。 它是一个抽象类。它提供控制对临界区的访问和对被阻塞等待访问临界区的线程队列的管理的操作。 这些操作基于以下2个抽象方法:

  • tryAcquire(): 此方法被调用以尝试获得对一个临界区的访问权限。
    • 如果调用它的线程能访问临界区,则此方法返回 true
    • 否则返回 false
  • tryRelease(): 此方法被调用以尝试释放对一个临界区的访问权限。
    • 如果调用它的线程能释放此权限,则此方法返回 true
    • 否则返回 false

您必须在这些方法中实现用以控制对临界区访问的机制。 在本例中,实现了扩展 AbstractQueuedSyncrhonizer 类的 MyQueuedSynchonizer 类, 并用一个 AtomicInteger 变量来控制对临界区的访问c从而实现了前述抽象方法。

  • 如果锁可用,该变量将保存0值,线程可以访问临界区;
  • 如果锁被阻塞,该变量保存1,线程不能访问临界区。

您已使用 AtomicInteger 类的 compareAndSet() 方法。该方法尝试将第一个参数的值与自身的值比较,如果想等则改为第二个参数的值。

  • 为了实现 tryAcquire() 方法,尝试将 atomic 变量的值从0改成1
  • 为了实现 tryRelease() 方法,尝试将 atomic 变量的值从1改成0

您必须实现 MyQueuedSynchonizer 类, 因为 AbstractQueuedSynchronizer 类的其他实现(如 ReentrantLock 使用的)都是作为使用它的类的内部私有类实现的, 所以您没有权限使用它们。



然后,实现了 MyLock 类。此类实现了 Lock 接口并有一个 MyQueuedSynchronizer 对象属性。 为了实现 Lock 接口的所有方法,使用了 MyQueuedSynchronizer 对象的方法。

最后,实现了 Task 类。它实现了 Runnable 接口并使用一个 MyLock 对象以访问临界区。此临界区将线程催眠2秒。

Main 类创建一个 MyLock 对象并运行 10 个 Task 对象共享此锁。Main 类也用 tryLock() 方法尝试获得对此锁的权限。

运行此样例,您能看到只有一个线程有对临界区有权限访问,当线程结束后,其他的获得权限。

您能使用您自己的 Lock 来输出关于它的使用的信息到日志、控制锁的时间、或实现更高级的同步机制(例如控制对一个资源的访问这样只在特定的时间才可用)。

了解更多

AbstractQueuedSynchronizer 类提供了2个方法能被用于管理锁的状态:getState() 和 setState() 方法。 这些方法接受和返回一个整数值代表锁的状态。您能使用那些方法替代 AtomicInteger 属性来保存锁的状态。

Java Concurrency API 提供了另一个类来实现同步机制:AbstractQueuedLongSynchronizer 类, 等同于 AbstractQueuedSynchronizer 类,但是用一个 long 属性来保存线程状态。