首先扯点别的:记得以前在大学校里和同学一起打勾级,我怂恿我的队友杜仁建出牌,硬说我队友的对门周通要不了杜仁建的牌,原话是这样的“他(周通)要是能要了,我把牌吃了吐出来再吃”。结果他(周通)还是把我队友(杜仁建)给闷了。现在想想也是有意思。
在Java中为了控制多个线程对共享资源的访问,有两种方式。
- 使用synchronized关键字,通过同步方法或者同步代码块的方式。
- 使用Lock,通过显式的获取锁和释放锁来实现。
今天记录一下Java中Lock的使用。
在大多数情况下,应使用以下惯用法来使用Lock:
Lock l = ...;
l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();
}
}
首先看一下Lock这个接口,在java.util.concurrent.locks
包下面。该接口定义了获取锁和释放锁的方法。
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
//释放锁
void unlock();
//返回一个和当前锁关联的Condition实例
Condition newCondition();
}
解释一下上面几个方法的作用
void lock();
获取锁,如果锁不可用,当前线程则不能被线程调度,并处于休眠状态,直到获得锁。
void lockInterruptibly() throws InterruptedException;
获取锁,如果锁可用则立即返回。如果锁不可用,当前线程则不能被线程调度,并处于休眠状态,直到下面两种情况之一发生。
- 当前线程获取到锁。
- 其他线程中断了当前线程,则抛出InterruptedException并且当前线程的中断状态被清除。
boolean tryLock();
获取锁,如果当前锁可用立即返回true,不可用则立即返回false。也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。使用tryLock的惯用语法。
Lock lock = ...;
if (lock.tryLock()) {
try {
// 获取成功操作
} finally {
lock.unlock();
}
} else {
//获取失败操作
}}
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
在指定时间内并且当前线程没有被中断的情况下获取锁。
如果锁可用立即返回true。如果锁不可用,当前线程会一直休眠等待直到下面三种情况中的一种情况发生。
- 当前线程成功获取了锁,返回true。
- 当前线程被其他线程中断,抛出InterruptedException异常并且当前线程的中断状态被清除。
- 等待时间超时,返回false。
//释放锁
void unlock();
//返回一个和当前锁关联的Condition实例
Condition newCondition();
下面看一下Lock的两个实现类。
ReentrantLock 可重入锁
ReentrantLock类的定义:具有与使用同步方法和语句访问的隐式监视器锁相同的基本行为和语义的可重入互斥锁,但具有扩展功能。
通俗一点说就是ReentrantLock可以实现同步方法和同步代码块同样的功能,而且还具有其他扩展功能。
ReentrantLock类实现了Lock接口,并且增加了许多有用的方法。
ReentrantLock类的部分方法
public int getHoldCount() {
return sync.getHoldCount();
}
获取当前线程对该锁的保持次数;如果当前线程未保持此锁,则为零。这里可以认为锁有一个计数器,一个线程可以多次获取锁(这就是可重入锁),每获取一次锁,锁中的计数器就会加1,线程每释放一次锁,锁中的计数器就会减1,当锁中的计数器减到0的时候,说明当前线程释放了锁。
protected Thread getOwner() {
return sync.getOwner();
}
获取当前持有这个锁的线程,如果没有线程持有,则返回null。
public boolean isLocked() {
return sync.isLocked();
}
判断锁是否被某个线程持有。
public boolean isHeldByCurrentThread() {
return sync.isHeldExclusively();
}
判断锁是否被当前线程持有。
public final boolean isFair() {
return sync instanceof FairSync;
}
判断当前锁是否是公平锁
以ReentrantLock为例,看看Lock的具体的使用方法
lock()方法
public class Test {
private ArrayList<Integer> list = new ArrayList<>();
private Lock lock = new ReentrantLock();
public static void main(String[] args) {
Test test = new Test();
new Thread(new Runnable() {
@Override
public void run() {
test.insert(Thread.currentThread());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test.insert(Thread.currentThread());
}
}).start();
}
public void insert(Thread t) {
lock.lock();
try {
System.out.println(t.getName() + "得到了锁");
for (int i = 0; i < 5; i++) {
list.add(i);
}
} finally {
System.out.println(t.getName() + "释放了锁");
lock.unlock();
}
}
}
输出结果
Thread-0得到了锁
Thread-0释放了锁
Thread-1得到了锁
Thread-1释放了锁
tryLock()方法
public class Test {
private Lock lock = new ReentrantLock();
public static void main(String[] args) {
Test test = new Test();
new Thread(new Runnable() {
@Override
public void run() {
test.insert(Thread.currentThread());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
test.insert(Thread.currentThread());
}
}).start();
}
public void insert(Thread t) {
if (lock.tryLock()) {
try {
System.out.println(t.getName() + "得到了锁");
try {
//睡眠一会,但是不释放锁
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
System.out.println(t.getName() + "释放了锁");
lock.unlock();
}
} else {
System.out.println(t.getName() + "获取锁失败");
}
}
}
输出结果
Thread-0得到了锁
Thread-1获取锁失败
Thread-0释放了锁
lockInterruptibly()方法
public class Test {
private Lock lock = new ReentrantLock();
public static void main(String[] args) {
Test test = new Test();
MyThread thread1 = new MyThread(test);
MyThread thread2 = new MyThread(test);
thread1.start();
//延迟一会再启动线程2,否则可能出现线程2先被调度执行,那么线程2就不能被中断了
try {
Thread.sleep(2000);
System.out.println("启动线程" + thread2.getName());
thread2.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//中断thread2
thread2.interrupt();
}
public void insert(Thread thread) throws InterruptedException {
lock.lockInterruptibly();
try {
System.out.println(thread.getName() + "获得了锁");
long startTime = System.currentTimeMillis();
for (; ; ) {
if (System.currentTimeMillis() - startTime >= Integer.MAX_VALUE)
break;
//插入数据
}
} finally {
System.out.println(Thread.currentThread().getName() + "执行finally");
lock.unlock();
System.out.println(thread.getName() + "释放了锁");
}
}
}
class MyThread extends Thread {
private Test test = null;
public MyThread(Test test) {
this.test = test;
}
@Override
public void run() {
try {
test.insert(Thread.currentThread());
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "被中断");
}
}
}
输出结果
Thread-0获得了锁
Thread-1被中断
当thread1启动后,获得了锁。两秒后再启动thread2,然后thread2尝试获取锁,这时候,thread1正在持有锁,所以thread2处于等待状态。然后又过了两秒,调用thread2.interrupt()方法,中断thread2。lock.lockInterruptibly();就会抛出中断异常,在捕获的异常中输出Thread-1被中断。
ReadWriteLock
ReadWriteLock:读写锁,也是一个接口,在java.util.concurrent.locks
包下,内部维持一对相关的锁。读锁,可以多个读者线程共用。写锁,互斥,同一时刻只能被一个线程使用。
public interface ReadWriteLock {
//返回用来读的锁
Lock readLock();
//返回用来写的锁
Lock writeLock();
}
ReentrantReadWriteLock实现了ReadWriteLock接口。
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {
//内部类,提供读锁
private final ReentrantReadWriteLock.ReadLock readerLock;
//内部类,提供写锁
private final ReentrantReadWriteLock.WriteLock writerLock;
//...
public ReentrantReadWriteLock() {
this(false);
}
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
//获取写锁
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
//获取读锁
public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }
//ReadLock实现了Lock接口
public static class ReadLock implements Lock, java.io.Serializable {
}
//WriteLock实现了Lock接口
public static class WriteLock implements Lock, java.io.Serializable {
}
}
ReentrantReadWriteLock.WriteLock的获取锁的方法,以最简单的lock( )方法为例
/**
* 获取写锁
* 如果读锁和写锁都没有被其他线程持有,则立即返回,并设置锁的计数为1。
* 如果当前线程持有写锁,就把锁的计数加1,立即返回。
* 如果写锁被其他线程持有,当前线程就不能被线程调度,并一直休眠,直到获取写锁
*(获取到锁的时候将写锁的计数置为1)
*/
public void lock() {
sync.acquire(1);
}
ReentrantReadWriteLock.ReadLock获取锁的方法,以最简单的lock( )方法为例
//获取读锁
//如果写锁没有没其他线程持有,则立即返回。
//如果写锁被别的线程持有,则当前线程不能被线程调度,并一直休眠直到获取到读锁。
public void lock() {
sync.acquireShared(1);
}
多个线程同时使用读锁
public class ReentrantReadWriteLockTest {
private ReadWriteLock rwl = new ReentrantReadWriteLock();
public static void main(String[] args) {
ReentrantReadWriteLockTest test = new ReentrantReadWriteLockTest();
new Thread() {
public void run() {
test.get(Thread.currentThread());
}
}.start();
new Thread() {
public void run() {
test.get(Thread.currentThread());
}
}.start();
}
private void get(Thread thread) {
rwl.readLock().lock();
try {
long start = System.currentTimeMillis();
while (System.currentTimeMillis() - start < 1000) {
System.out.println(thread.getName() + "正在进行读操作");
}
System.out.println(thread.getName() + "读操作完毕");
} catch (Exception e) {
e.printStackTrace();
} finally {
rwl.readLock().unlock();
}
}
}
输出结果会发现两个线程会交替输出,意思就是多个线程可以同时共用读锁。
一个线程使用读锁,一个线程使用写锁。
如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁。如果有一个线程已经占用了写锁,则此时其他线程如果要申请读锁,则申请读锁的线程会一直等待释放写锁。
public class ReentrantReadWriteLockTest {
private ReadWriteLock rwl = new ReentrantReadWriteLock();
public static void main(String[] args) {
ReentrantReadWriteLockTest test = new ReentrantReadWriteLockTest();
new Thread() {
public void run() {
test.write(Thread.currentThread());
}
}.start();
new Thread() {
public void run() {
test.read(Thread.currentThread());
}
}.start();
}
private void read(Thread thread) {
rwl.readLock().lock();
try {
for (int i = 0; i < 100; i++) {
System.out.println(thread.getName() + "正在进行读操作" + i);
}
System.out.println(thread.getName() + "读操作完毕");
} catch (Exception e) {
e.printStackTrace();
} finally {
rwl.readLock().unlock();
}
}
private void write(Thread thread) {
rwl.writeLock().lock();
try {
for (int i = 0; i < 100; i++) {
System.out.println(thread.getName() + "正在进行写操作" + i);
}
System.out.println(thread.getName() + "写操作完毕");
} catch (Exception e) {
e.printStackTrace();
} finally {
rwl.writeLock().unlock();
}
}
}
运行结果发现读和写是顺序执行的,也就是说,读锁和写锁是互斥的,同一时刻不能有一个线程使用读锁,一个线程使用写锁。
结尾:先记录下来,部分内容是直接摘抄自 Java并发编程:Lock。以后再慢慢体会,完善。
参考
- Java并发编程:Lock
- 《疯狂Java讲义》