Lock
接口提供了与 synchronized
相似的功能,和 synchronized
不同的是,Lock
在使用的时候需要显示的获取和释放锁。虽然牺牲了隐式获取释放锁的便捷性,但是对于锁的操作具有更强的可操作性、可控制性以及提供可中断操作和超时获取锁等机制。本文将描述 ReentrantLock
和 ReadWriteLock
锁的基础使用。
一、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() | 创建一个 Condition ,Condition 能够更加精细的控制多线程的休眠与唤醒 |