假设这样一个情况,有多个用户(多线程)同时订同一个电影院的座位(访问同一资源),为了保证用户得到的座位是有效的,那么同一时间只能有一个用户订座。这里就需要通过锁控制线程获取座位信息。简化代码如下:
private val lock = ReentrantLock()
fun main() {
Thread { accessRes() }.start()//用户1
Thread { accessRes() }.start()//用户2
Thread { accessRes() }.start()//用户3
Thread { accessRes() }.start()//用户4
//...
}
private fun accessRes() {
lock.lock()
try {
//access resource
} finally {
lock.unlock()
}
}
注意 accessRes()中lock.unlock()被try{}catch{}包起来,这是为了防止accessRes()中代码发生crash,lock.unlock()没有调用,导致锁没有被释放,那么其他线程就再也无法获取锁并访问这些代码。
ReentrantLock:Re-entrant Lock,lock.lock()方法是可以多次被调用的,lock.unlock()也必须成对匹配出现:
private fun accessRes() {
lock.lock()
lock.lock()
//access resource
val holdCount = lock.holdCount
lock.unlock()
lock.unlock()
}
这当然是一个为了说明这种情况而举的例子,实际情况是如果在accessRes()中出现递归调用的情况,那么就会出现同一个线程重复调用lock.lock()方,这时并不会再次获取线程锁,只会增加lock.holdCount的数量:
private fun accessRes() {
lock.lock()
//access resource
val holdCount = lock.holdCount
if(some condition){
accessRes()
}
lock.unlock()
}
ReentrantLock在构造的时候传入一个参数ReentrantLock(boolean fair)表明该锁是否是公平锁。关于公平锁的概念可以通过上面那个例子理解:
Thread { accessRes() }.start()//用户1
Thread { accessRes() }.start()//用户2
Thread { accessRes() }.start()//用户3
Thread { accessRes() }.start()//用户4
如果用户1正在访问资源,那么后面的用户2、3、4...都会处于等待状态。这时如果用户1访问结束调用了lock.unlock()方法,公平锁会按线程等待的先后顺序(用户2)给予线程的锁权限,即在等待队列中谁在前面谁就会优先获取锁。如果是非公平锁呢?如果在用户1调用了lock.unlock()方法时正好有个用户5尝试获取线程锁,和公平锁会把用户5放到等待队列中并让用户2获取锁不同,为了加快速度,这里会让用户5优先获取锁。这里就是公平锁和非公平锁的区别。
ReentrantLock还有一个lock.tryLock()方法(有参/无参),用于尝试获取锁,并获取尝试获取锁的状态(是否成功),如果没有获取锁的话就不要block当前线程:
private fun accessRes() {
val tryLock = lock.tryLock()//doesn't honor fairness, event if the lock is fairness
val tryLock1 = lock.tryLock(10, TimeUnit.SECONDS)// honor fairness
if (tryLock) {
try {
//access resource
} finally {
lock.unlock()
}
} else {
//do something else
}
}
lock.tryLock()方法有参和无参最大的区别不仅仅在于有参的方法可以提供一个超时机制,更重要的是在于他们对锁公平性的影响。如果使用无参lock.tryLock()即使该锁是公平锁这个方法也不保证锁的公平性,即会变成一个非公平锁。有参lock.tryLock()则不存在这样的问题。
还是上面多人订座的例子,其实更符合用户使用的场景是同时有多个人在查看座位,同时只能有一个人订座位。但上面的例子就限制了所有用户中只要有一个用户查看座位,其他用户只能被block住连查看都不行,这显然是不符合用户的使用场景的。解决这个问题的方法就是使用ReadWriteLock。
ReadWriteLock和ReentrantLock的不同点在于,ReentrantLock同时只能有一个线程访问,而ReadWriteLock可以允许多个读(read)线程访问或一个写(write)线程访问。这样就可以实现上面提到的多个线程读取信息或只有一个线程写入信息。读和写分别需要通过相应的锁去控制,简化代码如下:
private val readWriteLock = ReentrantReadWriteLock()
private val writeLock = readWriteLock.writeLock()
private val readLock = readWriteLock.readLock()
private fun readRes() {
readLock.lock()
//read res only
readLock.unlock()
}
private fun writeRes() {
writeLock.lock()
//update res
writeLock.unlock()
}
private fun res() {
Thread { readRes() }.start()//1
Thread { readRes() }.start()//2
Thread { writeRes() }.start()//3
Thread { writeRes() }.start()//4
}
线程1和线程2同时访问readRes(),readLock可以让两个线程同时访问资源,但线程3和线程4如果同时访问资源,只能有一个线程获得访问权限。注意,如果线程3获得锁访问资源,这时如果线程1想要访问readRes()时是会被block住的。即读和写只能有一个被一个线程获取,但读可以被多个线程获取。












网友评论