synchronized想来大家都比较熟悉,这是java的一个关键字,也就是java的一个内置特性。但既生瑜何生亮,既然有了synchronized来实现同步,为何还要新增Lock,想来Lock肯定有一些synchronized没有的优点。
为什么要有Lock
用过synchronized的都知道,当一个代码块或一个方法被synchronized修饰时,当线程获取到锁并执行这段代码时,其它线程只能一直等待,直到获取锁的线程释放。那么,它将什么时候释放?
一般来说,线程释放锁只有两种情况:
1.synchronized修饰的方法或代码块已执行完;
2.线程执行异常,此时JVM不得不让线程释放锁。
但很多时候,我们不想让线程一直等待,我们想要的是未获取锁的进程只需等待一段时间后就可以执行线程操作。或者有一种情景:多个线程读写操作是冲突的,但多个线程同时读是不冲突,而我们又不想让读操作异步乱序地读取。显然,用synchronized无法满足我们的需求。
这时,Lock出现了。Lock可以实现synchronized的所有功能,同时也能满足我们的需求,线程获取锁时,其它线程不用一直等待,同时进行读取操作时不乱序。
但是,有一点需要注意:Lock不是java内置的一个特性,无法自动释放锁,因此Lock必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
如何使用Lock
Lock是一个接口,因此我们使用时必须使用它的唯一实现类ReentrantLock。ReentrantLock也就是可重用锁,那什么是可重入锁呢?
简单来说,可重入锁是基于线程的分配,而不是基于方法调用的分配。举个简单的例子,当一个线程执行到某个synchronized方法时,比如说method1,而在method1中会调用另外一个synchronized方法method2,此时线程不必重新去申请锁,而是可以直接执行方法method2。
一个栗子:
package com.yixingu.lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockDemo {
Lock lock = new ReentrantLock();//lock存储在堆中,各个线程共享,才会发生冲突,实现同步效果
public static void main(String[] args) {
LockDemo demo = new LockDemo();
new Thread(){
public void run() {
demo.test();
};
}.start();
new Thread(){
public void run() {
demo.test();
};
}.start();
}
public void test(){
lock.lock();
try{
System.out.println(Thread.currentThread().getName()+"获取了锁");
}finally {
lock.unlock();
System.out.println(Thread.currentThread().getName()+"释放了锁");
}
}
}
结果:
Thread-0获取了锁
Thread-0释放了锁
Thread-1获取了锁
Thread-1释放了锁
接下来我们看看tryLock的效果,为了实现这个效果,我特意让线程休眠两秒:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockDemo {
Lock lock = new ReentrantLock();// lock存储在堆中,各个线程共享,才会发生冲突,实现同步效果
public static void main(String[] args) {
LockDemo demo = new LockDemo();
new Thread() {
public void run() {
demo.trytest();
};
}.start();
new Thread() {
public void run() {
demo.trytest();
};
}.start();
}
public void trytest() {
if (lock.tryLock()) {
try {
System.out.println(Thread.currentThread().getName() + "获取了锁");
Thread.sleep(2000);
} catch (Exception e) {
} finally {
lock.unlock();
System.out.println(Thread.currentThread().getName() + "释放了锁");
}
} else {
System.out.println(Thread.currentThread().getName() + "获取锁失败");
}
}
运行结果:
Thread-0获取了锁
Thread-1获取锁失败
Thread-0释放了锁
读写锁ReadWriteLock
ReadWriteLock也是一个接口,它里面只定义了两个方法:readLock()和writeLock()。同理,使用时,我们使用它的实现类ReentrantReadWriteLock。
一个栗子:
package com.yixingu.lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockDemo {
ReadWriteLock lock = new ReentrantReadWriteLock();
public static void main(String[] args) {
ReadWriteLockDemo demo = new ReadWriteLockDemo();
new Thread() {
public void run() {
demo.get();
};
}.start();
new Thread() {
public void run() {
demo.get();
};
}.start();
}
public void get() {
lock.readLock().lock();
try {
int count = 15;
while (count > 0) {
System.out.println(Thread.currentThread().getName() + "正进行读操作");
count --;
}
System.out.println(Thread.currentThread().getName() + "读操作完毕");
} finally {
lock.readLock().unlock();
}
}
}
synchronized来实现:
try {
int count = 15;
while (count > 0) {
System.out.println(Thread.currentThread().getName() + "正进行读操作");
count --;
}
System.out.println(Thread.currentThread().getName() + "读操作完毕");
} finally {
}
}
总结
1)Lock是一个接口,而synchronized是Java中的关键字,是java内置的特性;
2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
5)Lock可以提高多个线程进行读操作的效率。
其实,两者在竞争不太激烈的情况下性能是差不多的,甚至vsynchronized会更好一点,但在竞争激烈的情况下,不用考虑了,用Lock吧!