昨天面试,面试官问了自己一个synchronized和ReentrantLock的区别,感觉自己回答的并不是特别好,今天在翻书学习总结一下,毕竟书读百遍其义自见。
开始进入正题
两者的共同点:
1)协调多线程对共享对象、变量的访问
2)可重入,同一线程可以多次获得同一个锁
3)都保证了可见性和互斥性
两者的不同点:
1)ReentrantLock显示获得、释放锁,synchronized隐式获得释放锁
2)ReentrantLock可响应中断、可轮回,synchronized是不可以响应中断的,为处理锁的不可用性提供了更高的灵活性
3)ReentrantLock是API级别的,synchronized是JVM级别的
4)ReentrantLock可以实现公平锁
5)ReentrantLock通过Condition可以绑定多个条件
6)底层实现不一样, synchronized是同步阻塞,使用的是悲观并发策略,lock是同步非阻塞,采用的是乐观并发策略
虽然ReentrantLock可以提供比synchronized更高级的功能,但是仍不能替换synchronized
为什么呢?
《java并发编程实战》上说是因为如果使用reentrantlock时,你没有释放锁,很难追踪到最初发生错误的位置,因为没有记录应该释放锁的位置和时间。网上找了一下,没有找到其他比较合理的答案,先暂且记住吧
几个方法:
1) boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
time:等待锁定的最长时间
unit: 时间单位
这个方法起到了定时锁的作用,如果在指定时间内没有获取到锁,将会返回false
应用:具有时间限制的操作时使用
一个简单例子:
public class TestReentrantLock {
public static void main(String[] args) {
Lock r = new ReentrantLock();
//线程1
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
//获得锁
r.lock();
try {
System.out.println("线程1获得了锁");
//睡眠5秒
Thread.currentThread().sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
r.unlock();
}
}
});
thread1.start();
//线程2
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
if (r.tryLock(1000, TimeUnit.MILLISECONDS)) {
System.out.println("线程2获得了锁");
} else {
System.out.println("获取锁失败了");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread2.start();
}
}
运行结果:
线程1获得了锁
获取锁失败了
此时线程2在1秒之内没有获得到锁
boolean tryLock();
只有在获得锁的情况下才会返回true,可以通过使用这个方法先判断一下能否获得锁,避免长时间获不到锁的等待,以及死锁的产生。这个方法只有一次尝试获得锁的机会。
void lockInterruptibly() throws InterruptedException;
获得可中断锁。可中断锁可以响应中断,可以让它中断自己或者在别的线程中中断它,中断后可以放弃等待,去处理其他事,而不可中断锁不会响应中断,将一直等待,synchronized就是不可中断。
举例子:
使用synchronized关键字
Buffer类:
public class Buffer {
private Object lock;
public Buffer() {
lock = this; //buffer自身
}
public void write() {
synchronized (lock) {
long startTime = System.currentTimeMillis();
System.out.println("往这个buffer写入数据...");
//死循环模拟要处理很长时间
for(;;) {
if (System.currentTimeMillis() - startTime > Integer.MAX_VALUE) {
break;
}
}
System.out.println("终于写完了");
}
}
public void read() {
synchronized (lock) {
System.out.println("从这个buff读数据");
}
}
public static void main(String[] args) {
Buffer buffer = new Buffer();
final Writer writer = new Writer(buffer);
final Reader reader = new Reader(buffer);
//启动线程
writer.start();
reader.start();
//尝试启动一个线程去中断reader线程
new Thread(new Runnable() {
@Override
public void run() {
long start = System.currentTimeMillis();
for(;;) {
//等待五秒钟去中断
if (System.currentTimeMillis() - start > 5000) {
System.out.println("不等了,尝试中断");
reader.interrupt();
break;
}
}
}
}).start();
}
}
Reader类:
public class Reader extends Thread {
private Buffer buffer;
public Reader(Buffer buffer) {
this.buffer = buffer;
}
public void run() {
buffer.read(); //这里将会引起长时间的阻塞
System.out.println("读结束");
}
}
Writer类:
public class Writer extends Thread {
private Buffer buffer;
public Writer(Buffer buffer) {
this.buffer = buffer;
}
public void run() {
buffer.write();
}
}
reader类没有被中断
使用ReentrantLock获得中断锁
BufferInterruptibly类:
public class BufferInterruptibly {
private Lock lock = new ReentrantLock();
public void write() {
lock.lock();
try {
long start = System.currentTimeMillis();
System.out.println("开始往这个buff写入数据...");
//模拟要处理很长时间
for (;;) {
if (System.currentTimeMillis() - start > Integer.MAX_VALUE) {
break;
}
}
System.out.println("终于写完了");
} finally {
lock.unlock();
}
}
public void read() throws InterruptedException {
lock.lockInterruptibly(); //获得可中断锁
try {
System.out.println("从这个buff读数据");
} finally {
lock.unlock();
}
}
public static void main(String args[]) {
BufferInterruptibly buff = new BufferInterruptibly();
final Writer2 writer = new Writer2(buff);
final Reader2 reader = new Reader2(buff);
writer.start();
reader.start();
new Thread(new Runnable() {
@Override
public void run() {
long start = System.currentTimeMillis();
for (;;) {
if (System.currentTimeMillis()
- start > 5000) {
System.out.println("不等了,尝试中断");
reader.interrupt(); //此处中断读操作
break;
}
}
}
}).start();
}
}
Reader2类
public class Reader2 extends Thread {
private BufferInterruptibly buff;
public Reader2(BufferInterruptibly buff) {
this.buff = buff;
}
@Override
public void run() {
try {
buff.read();//可以收到中断的异常,从而有效退出
} catch (InterruptedException e) {
System.out.println("我不读了");
}
System.out.println("读结束");
}
}
Writere2类:
public class Writer2 extends Thread {
private BufferInterruptibly buff;
public Writer2(BufferInterruptibly buff) {
this.buff = buff;
}
@Override
public void run() {
buff.write();
}
}
运行结果:
开始往这个buff写入数据...
不等了,尝试中断
我不读了
读结束
ReentrantLock的公平性
在ReentrantLock中的构造函数中,提供了一个参数,指定是否为公平锁。
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
如果指定了参数为true,默认为非公平锁。
公平锁:线程将按照它们发出的请求顺序来获得锁
非公锁:当一个线程请求非公平锁的时候,如果发出请求时,获得锁的线程刚好释放锁,则该线程将会获得锁而跳过在该锁上等待的线程。
一个公平锁例子:
public class TestReentrantLock2 {
private static Lock lock = new ReentrantLock(true); //lock为公平锁
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
try {
System.out.println("线程1启动...");
} finally {
lock.unlock();
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
try {
System.out.println("线程2启动...");
} finally {
lock.unlock();
}
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
try {
System.out.println("线程3启动...");
} finally {
lock.unlock();
}
}
});
t1.start();
t3.start();
t2.start();
}
}
运行结果:
线程1启动...
线程3启动...
线程2启动...
什么时候选择使用synchronized,什么使用选择使用ReentrantLock
仅当synchronized不能满足时才使用ReentrantLockk,因为使用ReentrantLock要非常小心,不释放锁将影响其他需要该锁的代码块运行
不能使用synchronized不满足的情形:
1)公平性
2)可中断
4)分块结构的加锁,比如jdk1.7ConcurrentHashMap的分段锁(目前还不是提别理解这个,先记住这个例子,后头补充)
synchronized和ReentrantLock两者之间性能的比较
从jdk1.5以后,性能就差不多了,因为jvm对synchronized进行了很多优化