内部所拥有比较好的性能,但是在灵活性方面有缺陷,并且如果申请锁失败就会陷入阻塞等待的过程中。
对于一些场景,我们可以使用显示锁Lock
基本应用
Lock 的lock方法相当于进入同步块, unlock方法相当于退出同步块。支持跟内部锁同样的重入机制:
public void displayLock(){
Lock lock = new ReentrantLock();
lock.lock();
try{
}finally{
lock.unlock();
}
}
public void innerLock(){
Object lock = new Object();
synchronized (lock){
}
}
可以看出内部锁的代码还有清爽一点, 显示锁需要在finally中释放
tryLock避免死锁
先看下死锁的情况:
public class TestLock {
public static void main(String[] args) {
final Accout accout1 = new Accout(20);
final Accout accout2 = new Accout(20);
final TestLock testLock = new TestLock();
new Thread(){
@Override
public void run() {
testLock.transferMoney(accout2, accout1, 10);
}
}.start();
testLock.transferMoney(accout1, accout2, 10);
}
public void transferMoney(Accout accout1, Accout accout2, int money){
synchronized (accout1){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (accout2){
accout1.add(money);
accout2.decrease(money);
System.out.println(accout1.getAccout());
System.out.println(accout2.getAccout());
}
}
}
}
class Accout{
public Accout(int accout){
this.accout = accout;
}
public Lock lock = new ReentrantLock();
private int accout;
public void add(int money){
accout += money;
}
public void decrease(int money){
accout -= money;
}
public int getAccout() {
return accout;
}
}
改为不会死锁的情况:
public void transferMoney(Accout accout1, Accout accout2, int money){
if(accout1.lock.tryLock()){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(accout2.lock.tryLock()){
accout1.add(money);
accout2.decrease(money);
System.out.println(accout1.getAccout());
System.out.println(accout2.getAccout());
accout2.lock.unlock(); // sould be in finally block
}
accout1.lock.unlock(); // sould be in finally block
}
}
如上其实主要是利用了tryLock当获取不到锁就返回false的特性。另外tryLock还能设置超时时间,指定时间得不到锁才返回false.
tryLock可中断锁
当代码遇到锁标记有很多种选择,获得锁,如果锁不再了等待锁,重入进该锁, 响应中断。
通常的锁在需要等待的时候并不会响应中断。
如下:
public class TestLock {
public static void main(String[] args) {
new TestLock().testUnInterrupt();
}
public void testUnInterrupt(){
Lock lock = new ReentrantLock();
ThreadTest t1 = new ThreadTest(lock, "t1");
t1.start();
ThreadTest t2 = new ThreadTest(lock, "t2");
t2.start();
t2.interrupt();
}
}
class ThreadTest extends Thread{
private Lock lock;
public ThreadTest(Lock lock, String name){
super(name);
this.lock = lock;
}
@Override
public void run(){
lock.lock();
System.out.println(getName() + "运行开始");
// try {
// lock.lockInterruptibly();
// } catch (InterruptedException e) {
// System.out.println(getName() + "锁等待被中断");
// e.printStackTrace();
// }
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
System.out.println(getName() + "休眠被中断");
// e.printStackTrace();
} finally {
lock.unlock();
}
System.out.println(getName() + "运行完成");
}
}
使用lock()的返回结果:
t1运行开始
t2运行开始
t1运行完成
t2休眠被中断
t2运行完成
使用lockInterruptibly()的返回结果:
t1运行开始
t2运行开始
t2锁等待被中断
可见lockInterruptibly与普通锁的区别在于可以在锁等待的时候响应中断
lock和synchronized性能
在JDK6之前性能上Lock要好很多,但是JDK6之后改进了内部锁的算法,现在差不多
公平锁
ReentrantLock锁的构造器中可以选择是否公平,公平的意思就是开始执行的顺序将会按照锁等待的顺序。这样会造成性能下降,只有在对顺序敏感的时候才需要。
synchronized还是ReentrantLock
- 性能上JDK1.6,ReentrantLock略好。
- ReentrantLock可以支持复杂的语义。
- ReentrantLock需要显示关闭
- synchronized语法上更简单易用
综上,其实应该更多的使用synchoronized,只有在有必要的时候才使用ReentrantLock
读写锁
普通的锁限制了并发性,对于数据无害的读-读操作也会出现锁竞争。这个时候可以使用读写锁
读写锁的一些特性:
- 基本功能就是有写入的时候要等写完完成才能再执行写入,如果没有写入的时候读是可以并发的
- 可降级不可以升级,就是读解锁前如果获取写锁会死锁,但是写锁中是可以获取读锁的
- 各锁单独可重入
如下:
public class TestLock {
public static void main(String[] args) {
final Cache cache = new Cache();
new Thread(){
@Override
public void run() {
cache.add("1");
cache.add("2");
cache.add("3");
}
}.start();
new Thread(){
@Override
public void run() {
cache.get();
}
}.start();
new Thread(){
@Override
public void run() {
cache.get();
}
}.start();
}
}
class Cache{
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
List<String> datas = new ArrayList<String>();
public void add(String data){
rwl.writeLock().lock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
datas.add(data);
System.out.println("添加" + data);
rwl.writeLock().unlock();
}
public String get(){
rwl.readLock().lock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
String data = datas.get(0);
datas.remove(0);
rwl.readLock().unlock();
System.out.println("获得" + data);
return data;
}
}
可以验证写锁互斥,读写并发。
时间: 2024-12-24 20:56:14