1 简介
synchronized和ReentrantLock都是可重入锁,即单个线程中可以锁多次。
2 区别
ReentrantLock包含特性:
- 等待可中断
当持有锁的线程长期不释放锁,正在等待的线程可以选择放弃等待。
- 公平锁
多个线程在等待同一个锁,必须按照申请锁的顺序来依次获得锁;而非公平锁则没有限制。synchronized和ReentrantLock默认都是非公平锁,ReentrantLock可通过修改属性构建公平锁。
- 多条件控制
一个ReentrantLock对象可以绑定多个Condition对象,而一个synchronized关联的锁对象只能对应一个条件。
来一个典型的消费者生产者例子:
当生产仓库满时,生产者需要等待消费者消费之后才继续生产
当生产仓库空时,消费者需要等待生产者生产之后才继续消费
synchronized方式:
public class SyncProductTest {
public static volatile int productNum=7;
public static volatile int productMaxNum=10;
public static Object productor = new Object();
public static void main(String[] args) {
for(int i=0;i<20;i++){
new Thread(new ConsumeThread()).start();
}
for(int i=0;i<20;i++){
new Thread(new ProductThread()).start();
}
}
}
class ProductThread implements Runnable{
@Override
public void run() {
synchronized (SyncProductTest.productor) {
while(SyncProductTest.productNum==SyncProductTest.productMaxNum){
System.out.println(Thread.currentThread().getName()+"product is full");
try {
System.out.println(Thread.currentThread().getName()+"productor wait...");
SyncProductTest.productor.wait();
System.out.println(Thread.currentThread().getName()+"productor wait over...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
SyncProductTest.productNum++;
SyncProductTest.productor.notifyAll();
}
}
}
class ConsumeThread implements Runnable{
@Override
public void run() {
synchronized (SyncProductTest.productor) {
while(SyncProductTest.productNum==0){
System.out.println(Thread.currentThread().getName()+"product is empty");
try {
System.out.println(Thread.currentThread().getName()+"consumer wait...");
SyncProductTest.productor.wait();
System.out.println(Thread.currentThread().getName()+"consumer wait over...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
SyncProductTest.productNum--;
SyncProductTest.productor.notifyAll();
}
}
}
ReentrantLock方式:
public class LockProductTest {
public static volatile int productNum=7;
public static volatile int productMaxNum=10;
public static ReentrantLock productorLock = new ReentrantLock();
public static Condition productCondition = productorLock.newCondition();
public static Condition consumeCondition = productorLock.newCondition();
public static void main(String[] args) {
for(int i=0;i<20;i++){
new Thread(new ConsumeThread()).start();
}
for(int i=0;i<20;i++){
new Thread(new ProductThread()).start();
}
}
}
class ProductLockThread implements Runnable{
@Override
public void run() {
try{
LockProductTest.productorLock.lock();
while(LockProductTest.productNum==LockProductTest.productMaxNum){
System.out.println(Thread.currentThread().getName()+"product is full");
try {
System.out.println(Thread.currentThread().getName()+"productor wait...");
LockProductTest.productCondition.await();
System.out.println(Thread.currentThread().getName()+"productor wait over...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
LockProductTest.productNum++;
LockProductTest.consumeCondition.signalAll();
}finally{
LockProductTest.productorLock.unlock();
}
}
}
class ConsumeLockThread implements Runnable{
@Override
public void run() {
try{
LockProductTest.productorLock.lock();
while(LockProductTest.productNum==0){
System.out.println(Thread.currentThread().getName()+"product is empty");
try {
System.out.println(Thread.currentThread().getName()+"consumer wait...");
LockProductTest.consumeCondition.await();
System.out.println(Thread.currentThread().getName()+"consumer wait over...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
LockProductTest.productNum--;
LockProductTest.productCondition.signalAll();
}finally{
LockProductTest.productorLock.unlock();
}
}
}
第一种方式,生产者消费者通过同一个对象锁进行等待和唤醒,会照成无效唤醒,比如一个生产者线程唤醒了另一个生产者线程,一个消费者唤醒了另一个,而我们实际需要的是生产者线程生产后去唤醒消费者去消费,所以第二种分条件唤醒的方式更合适。
3 总结
- 如果需要细粒度的控制锁过程,可以选择使用ReentrantLock
- 如果不需要,建议1.5之后都使用synchronized,JVM一直在优化,应该是比自己处理Lock过程更可靠
爱家人,爱生活,爱设计,爱编程,拥抱精彩人生!