Java Lock的使用
首先扯点别的:记得以前在大学校里和同学一起打勾级,我怂恿我的队友杜仁建出牌,硬说我队友的对门周通要不了杜仁建的牌,原话是这样的“他(周通)要是能要了,我把牌吃了吐出来再吃”。结果他(周通)还是把我队友(杜仁建)给闷了。现在想想也是有意思。
今天记录一下Java中同步锁的使用。以后再慢慢理解。
首先看一下Lock这个接口,在java.util.concurrent.locks.Lock
包下面,ReentrantLock:可重入锁,实现了Lock接口。
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;
获取锁,如果锁可用则立即返回。如果锁不可用,当前线程则不能被线程调度,并处于休眠状态,直到下面两种情况之一发生。
1. 当前线程获取到锁。
2. 当前线程被中断,抛出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。
ReentrantLock类中其他方法
//判断锁是否被线程持有
public boolean isLocked() {
return sync.isLocked();
}
//判断当前锁是否是公平锁
public final boolean isFair() {
return sync instanceof FairSync;
}
具体的使用方法
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.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:读写锁,也是一个接口,在java.util.concurrent.locks.ReadWriteLock
包下,内部维持一对相关的锁。读锁,可以多个读者线程共用。写锁,互斥,同一时刻只能被一个线程使用。ReentrantReadWriteLock实现了这个接口。
public interface ReadWriteLock {
//返回用来读的锁
Lock readLock();
//返回用来写的锁
Lock writeLock();
}
public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable {
//内部类,提供读锁
private final ReentrantReadWriteLock.ReadLock readerLock;
//内部类,提供写锁
private final ReentrantReadWriteLock.WriteLock writerLock;
//...
//获取写锁
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
//获取读锁
public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }
}
WriteLock的获取锁的方法,以最简单的lock( )方法为例
/**
* 获取写锁
*如果读锁和写锁都没有被其他线程持有,则立即返回,并设置锁的计数为1。
*如果当前线程持有写锁,就把锁的计数加1,立即返回。
*如果写锁被其他线程持有,当前线程就不能被线程调度,并一直休眠,直到获取写锁(这时候写锁的计数为1)
*/
public void lock() {
sync.acquire(1);
}
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();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread() {
public void run() {
test.get(Thread.currentThread());
}
}.start();
}
private void get(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();
}
}
}
运行结果发现读和写是顺序执行的,也就是说,读锁和写锁是互斥的,同一时刻不能有一个线程使用读锁,一个线程使用写锁。
结尾:先记录下来,以后再慢慢体会,完善。
参考
【1】http://www.cnblogs.com/dolphin0520/p/3923167.html
【2】疯狂Java讲义