可重入锁

一 说明

java自1.5之后提供了一种新的同步互斥机制——可重入锁.相关的类位于java.util.concurrent.locks包下面.

二 类图

其中最重要的就是Lock和Condition这两个接口,这两个接口描述了一把锁应该具有的功能.Condition的作用一开始可能让人迷惑,不理解java语言设计人员设计这个接口的目的是什么.其实如果把Lock的定位是为了取代或补充synchronized关键,那么Condition接口对标的就是Object类的wait(),notify(),notifyAll()这三个方法.这样理解的话,Condition的作用和用法就非常明确和具体了.

当我第一次接触ReadWriteLock这个接口的时候,往往会被它的名字迷惑,因为这名字让我相信这个接口的肯定是对Lock这个接口的扩展,然后会包含两类方法: 以读的方式加锁的方法和以写的方式加锁的方法,但其实不是.这其实是如何设计读写锁的问题了.我的第一观念的来源可能是受到读写文件相关的接口设计的影响.很多语言的读写文件的接口的设计往往是先打开文件,再读写文件.而在打开文件的方法参数中往往会带一个打开方式的参数(读,写或读写),即在动作里面描述(在这个过程我们只看到访问者和资源).但是java在设计读写锁的时候使用了另一种方式.即把读写锁封装成一个锁,但是两种锁的存在均对外暴露,而两种锁的性质由锁本身自己实现.这样的好处是把锁的状态集中在一起,便于锁的实现,读锁和写锁之间的协作会很容易比如说,读锁和写锁之间可以轻松的了解对方的加锁状态以及加锁的线程数,这两个信息对当前是否可以上锁很有必要.如果分开的话,两个锁之间的协调就是一个很大的问题.

如果像打开文件那样,把锁的方式用参数传递,比如可以这样设计读写锁:

public interface ReadWriteLock extends Lock {
    public void lockRead();
    public void unlockRead();

    …其他方法…

}

这样的话就需要对Lock接口中的每个方法都提供一个读方式的接口方法,这样就显得很臃肿.所以java设计者直接把两种锁暴露出来,让程序员自己先去获取具体的锁,然后在对锁进行操作.从设计角度来看,ReadWriteLock并不是锁,而是两个锁的管理者.

三 可重入的内涵

四 Lock的使用方式

对于一把锁,很容易想到的两个基本功能就是加锁和解锁.lock()和unlock()就是对这两个基本功能的描述.如果再考虑等待锁的时候线程是否可以被中断,就可以再引入lockInterruptibly().如果再要请求锁的时候不阻塞,而是通过返回值来判断锁是否可用,所以又引入了tryLock()方法.

lock(): 请求锁,如果不可用则阻塞当前线程,直到锁可用为止.

lockInterruptibly(): 等待锁的时候该可以被其他线程中断,抛出InterruptedException.

boolean tryLock(): 非阻塞方式请求锁.

boolean tryLock(time, timeUnit): 非阻塞方式请求锁, 但是会等待一段时间,如果时间结束后,锁还不可用的话,就返回.

unlock(): 解锁.

newCondition(): 获得锁上的条件对象.

如何构造锁?

java设计者已经为我们提供了一个Lock的实现类,ReentrantLock, 创建一个ReentrantLock对象即可.

Lock的使用示例:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Data {
	private int flag;
	private Lock lock = new ReentrantLock();
	
	public int getFlag() {
		return flag;
	}

	public void setFlag(int flag) {
		try{
			lock.lock(); //加锁
			System.out.println(Thread.currentThread().getName() + " in.");
			Thread.sleep(3000);
			this.flag = flag;
			System.out.println(Thread.currentThread().getName() + " out.");
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			lock.unlock(); //释放锁, 一定要放在finally块中
		}
	}
}
class Runner implements Runnable{
	private Data data;
	private int value;
	public Runner(Data data, int value) {
		this.data = data;
		this.value = value;
	}
	
	@Override
	public void run() {
		data.setFlag(value);
	}
}
public class LockDemo {
	public static void main(String[] args) {
		Data data = new Data();
		Thread t1 = new Thread(new Runner(data, 1), "T1");
		Thread t2 = new Thread(new Runner(data, 2), "T2");
		t1.start();
		t2.start();
	}
}

输出结果:

有一点要注意: 释放锁的操作一定要放在finally块中. 和synchronized同步方法相比,锁虽然更加灵活,但是也带来了风险,用得不好,很可能就会导致死锁发生.

五 ReadWriteLock的使用

与Lock相比ReadWriteLock的read锁的lock()方法可以被多个线程调用而不阻塞(读共享),而write锁的lock()方法则与write锁的lock()和read锁的lock()是互斥的,即有各个操作已经发生,则会导致其他线程阻塞(写互斥)

使用读写锁的基本流程如下:

1)创建读写锁对象,java已经提供了ReentrantReadWriteLock类,可以创建一个这种类的对象.

2)拿到具体的锁对象,如获取读锁: Lock readLock = readwriteLock.readLock(); 获取写锁: Lock writeLock = readwriteLock.writeLock();

3)加锁,readLock.lock()或writeLock.lock()

4)释放锁.


与普通的锁在使用上的区别是: 读写锁在上锁之前要先拿到对应类型的锁.


ReadWriteLock使用示例:

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

class Disk {
	private ReadWriteLock lock = new ReentrantReadWriteLock();
	private String data = "";
	
	public String read() {
		try {
			System.out.println(Thread.currentThread().getName() + "准备读...");
			lock.readLock().lock();
			System.out.println(Thread.currentThread().getName() + "开始读...");
			Thread.sleep(30000);
			System.out.println(Thread.currentThread().getName() + "完成读...");
			return data;
		} catch(Exception e) {
			e.printStackTrace();
			return null;
		} finally {
			lock.readLock().unlock();
		}
	}
	
	public void write(String data) {
		try {
			System.out.println(Thread.currentThread().getName() + "准备写...");
			lock.writeLock().lock();
			System.out.println(Thread.currentThread().getName() + "开始写...");
			Thread.sleep(50000);
			this.data = data;
			System.out.println(Thread.currentThread().getName() + "写完成...");
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			lock.writeLock().unlock();
		}
	}
}

class Reader implements Runnable {
	private Disk disk;
	public Reader(Disk disk) {
		this.disk = disk;
	}
	@Override
	public void run() {
		String data = disk.read();
		System.out.println("从磁盘读到数据: " + data);
	}
}

class Writer implements Runnable {
	private Disk disk;
	public Writer(Disk disk) {
		this.disk = disk;
	}
	@Override
	public void run() {
		String data = System.currentTimeMillis() + "";
		disk.write(data);
		System.out.println("成功向磁盘写入数据: " + data);
	}
}

public class ReadWriteLockDemo {
	public static void main(String[] args) {
		Disk disk = new Disk();
		Thread r1 = new Thread(new Reader(disk), "R1");
		Thread r2 = new Thread(new Reader(disk), "R2");
		Thread w1 = new Thread(new Writer(disk), "W1");
		r1.start();
		w1.start();
		r2.start();
	}
}

运行结果:

六 Condition的使用

以后再补充.

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>