相信很多小伙伴们初学多线程的时候会被这两个名词搞晕,所以这里专门介绍这两种实现多线程锁的方式的区别和使用场景
Synchronized
这个关键词大家肯定都不陌生,具体的用法就是使用在对象、类、方法上
- 当使用在对象和对象方法上的时候,就会获取相应的对象锁
public synchronized void method() {
// do something
}
- 当使用在类、类属性、类方法上的时候,就会获取相应的类对象锁
public class A {
pubilc void method() {
synchronized(A.class) {
// do something
}
}
}
用法大概就是上面这两种了
使用synchronized
方法的好处就是很简单,上锁解锁都是自动完成的,代码的可读性也是很好的
但是,万事都有利弊,synchronized
的简单必然会导致他的灵活性会比较差
尤其是当我们想用到多个锁的时候,或者一个锁有多个条件的时候,这种方法都是难以实现的
所以下面我将介绍本文的主角,Lock对象
Lock
Lock本身是一个接口,有兴趣的小伙伴可以查看源码
在JDK中只有ReenterLock实现了Lock,而这个ReenterLock也就是我们常听到的重入锁
那么如何使用Lock呢,Lock又有哪些好处呢,下面一一道来
Lock的使用需要手动上锁解锁的
Lock lock = new ReenterLock();
lock.lock();
try {
//do something
} catch(IntruptException e) {
e.printStack();
} finally {
lock.unlock();
}
看起来麻烦了很多,但是麻烦是有回报的
首先,我们可以很*的获取和释放锁
其次,我们可以通过使用Condition来更加灵活地控制一个锁不咕不咕,绝不咕咕咕
我们下面分别举例说明这两个好处
①可以*获取和释放
给出下面一个场景
lock.lock();
if(/*condition*/) {
lock.unlock();
}
else {
// do someting
lock.unlock();
}
这个例子举的不是很恰当,因为不太符合Lock
的使用规范,但是表现出了这个优点,这种灵活性是synchronized
不具备的
②可以通过Condition
来更加灵活地控制一个锁
怎么个灵活呢,说白了就是可以有选择的睡眠和唤醒,下面给一个例子
背景是常见的生产者——消费者模型
我们希望当没有东西了消费者就等待,等待前唤醒生产者
东西满了生产者就等待,等待前唤醒消费者
要是用synchronized
就不好实现这个逻辑,因为我们其实只有一个临界资源,只是不同的条件而已
但是用Lock
就可以有效解决这个问题
Lock lock = new Lock();
Condition empty = lock.newCondition();
Condition full = lock.newCondition();
构造生产者和消费者的时候,将两个条件和锁传入
//Builder
lock.lock();
try {
if(isEmpty()) {
full.await();
}
// building...
if(isFull()) {
empty.await();
}
}
// Consumer
lock.lock();
try {
if(isFull()) {
empty.signalAll();
}
// consuming
if(isEmpty()) {
full.await();
}
}
这样,两个线程就可以通过两个Condition
来通信,虽然两个线程都使用同一个锁,但是每次唤醒的时候都是有选择地唤醒。
如果有多个消费者和生产者,那么,这种方式可以保证消费者每次唤醒的都是生产者,生产者每次唤醒的都是消费者。
初次原创博客,思路不是很清晰,本人也在不断学习中,如有错误、纰漏,欢迎大神指出(orz