Lock 接口提供了与 synchronized 相似的功能,和 synchronized 不同的是,Lock 在使用的时候需要显示的获取和释放锁。虽然牺牲了隐式获取释放锁的便捷性,但是对于锁的操作具有更强的可操作性、可控制性以及提供可中断操作和超时获取锁等机制。本文将描述 ReentrantLockReadWriteLock 锁的基础使用。

一、ReentrantLock 运用

package com.nineya.test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockMain {
    private int num;
    private int numLock;
    ReentrantLock lock = new ReentrantLock();

    public void execute() {
        num++;
    }

    public void executeLock() {
        lock.lock();
        numLock++;
        lock.unlock();
    }

    public void print() {
        System.out.println("num = " + num + ", numLock = " + numLock);
        num = 0;
        numLock = 0;
    }

    public static void main(String[] args) throws InterruptedException {
        ReentrantLockMain main = new ReentrantLockMain();
        ExecutorService executor = Executors.newCachedThreadPool();
        for (int j = 0; j < 5; j++) {
            for (int i = 0; i < 1000; i++) {
                executor.execute(main::execute);
                executor.execute(main::executeLock);
            }
            Thread.sleep(1000);
            main.print();
        }
    }
}

程序运行结果如下:

num = 986, numLock = 1000
num = 990, numLock = 1000
num = 998, numLock = 1000
num = 995, numLock = 1000
num = 995, numLock = 1000

众所周知,i++ 并不是原子操作,其执行过程可以分成读内存到寄存器、在寄存器中自增、写回内存三个部分,并发执行时就可能导致部分自增操作被覆盖,所以未加锁时 num 小于实际的值,加锁后的 numLock 值为正常。

二、ReadWriteLock 运用

ReadWriteLock 是独占锁和共享锁的实现,适用于读多写少的场景。

package com.nineya.test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockMain {
    private int num;
    private int numLock;
    ReadWriteLock lock = new ReentrantReadWriteLock();

    public int getNum() {
        return num;
    }

    public void read() {
        int a = getNum();
        int b = getNum();
        if (a != b) {
            System.out.println("num 改变: a = " + a + ", b = " + b);
        }
    }

    public void write() {
        num++;
    }

    public int getNumLock() {
        return numLock;
    }

    public void readLock(){
        lock.readLock().lock();
        int a = getNumLock();
        int b = getNumLock();
        if (a != b) {
            System.out.println("numLock 改变: a = " + a + ", b = " + b);
        }
        lock.readLock().unlock();
    }

    public void writeLock() {
        lock.writeLock().lock();
        numLock++;
        lock.writeLock().unlock();
    }

    public void print() {
        System.out.println("num = " + num + ", numLock = " + numLock);
        num = 0;
        numLock = 0;
    }

    public static void main(String[] args) throws InterruptedException {
        ReadWriteLockMain main = new ReadWriteLockMain();
        ExecutorService executor = Executors.newCachedThreadPool();
        for (int j = 0; j < 5; j++) {
            for (int i = 0; i < 1000; i++) {
                executor.execute(main::write);
                executor.execute(main::writeLock);
                executor.execute(main::read);
                executor.execute(main::readLock);
            }
            Thread.sleep(1000);
            main.print();
        }
    }
}

程序运行结果如下:

num 改变: a = 277, b = 278
num 改变: a = 322, b = 323
num 改变: a = 376, b = 377
num 改变: a = 422, b = 423
num = 997, numLock = 1000
num = 999, numLock = 1000
num = 998, numLock = 1000
num = 997, numLock = 1000
num = 999, numLock = 1000

可以看到,num 由于没有添加读锁,在两次值之间 num 的值发生了改变,添加读锁则可以解决该问题。

三、其他接口方法

方法功能
void lock()获取锁,如果该锁被另一个线程保持,则阻塞线程,直到拿到锁为止
void lockInterruptibly()
throws InterruptedException
调用后一直阻塞到获得锁,但是接受中断信号
boolean tryLock()尝试获取锁,非公平,不阻塞线程。如果获取锁成功或者已经持有锁,则返回 true,否则返回 false
boolean tryLock(long time, TimeUnit unit)
throws InterruptedException
在给定的等待时间内尝试获取锁,获取成功则立即返回 true。超过等待时间获取失败或者接收到中断信号则返回 false
void unlock()释放锁
Condition newCondition()创建一个 ConditionCondition 能够更加精细的控制多线程的休眠与唤醒