您的位置:首页 > 其它

Synchronized和ReentrantLock的区别

2017-12-08 19:28 726 查看
昨天面试,面试官问了自己一个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进行了很多优化
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  多线程