Lock同步锁
Lock是一个接口,用来手动的获取和释放锁,具体源码为
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
接下来逐个介绍它们的方法
- void lock(); 获取这个锁,如果锁同时被另一个线程拥有则发生阻塞
- void unlock(); 释放这个锁
- boolean tryLock(); 尝试获得锁而没有发生阻塞;如果成功返回真。这个方法会抢夺可用的锁,即使该锁有公平加锁策略,即使其他线程等很久。
- boolean tryLock(long time, TimeUnit unit); 尝试获得锁,阻塞时间不会超过给定的值;如果成功返回true。
- void lockInterruptibly(); 获得锁,但是会不确定地发生阻塞。如果线程被中断,跑出一个InterruptedException异常。
- Condition newCondition(); 返回一个与该锁相关的条件对象
Lock需要主动的获取锁,并且发生异常时不会主动的释放锁。所以实际应用中,Lock必须在try{}catch{}块中,在finally中释放锁,保证锁一定被释放,不会发生死锁。
接下来尝试用写一个自定义锁,只是实现获取锁和释放锁的部分。
package gemme.thread.lock;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public class MyLock implements Lock {
// 是否持有锁:默认为否
private boolean isHoldLock = false;
@Override
public synchronized void lock() {
if (isHoldLock) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
isHoldLock = true;
}
@Override
public synchronized void unlock() {
isHoldLock = false;
notify();
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock() {
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public Condition newCondition() {
return null;
}
}
尝试运行一下这段代码
package gemme.thread;
import gemme.thread.lock.MyLock;
import java.util.concurrent.locks.Lock;
public class UnSafeLock {
private static int num = 0;
private static Lock lock = new MyLock();
public static void add() {
lock.lock();
num++;
lock.unlock();
}
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
new Thread() {
@Override
public void run() {
add();
}
}.start();
}
try {
// 主线程可能会在其他线程还没结束时打印最终结果,所以让主线程先睡100毫秒
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(num);
}
}
最终结果为
1000
打印的最终结果是预期结果,利用实现了一个synchronized关键字很简单的同步锁。那么尝试运行下面的代码
package gemme.thread.lock;
import java.util.concurrent.locks.Lock;
public class ReentryDemo {
private static Lock myLock = new MyLock();
public void methodA() {
myLock.lock();
System.out.println("methodA");
methodB();
myLock.unlock();
}
public void methodB() {
myLock.lock();
System.out.println("methodB");
myLock.unlock();
}
public static void main(String[] args) {
ReentryDemo demo = new ReentryDemo();
demo.methodA();
}
}
发现打印出methodA后并没有打印methodB,问题在哪里呢?
methodA
第一次将isHoldLock置为true后,第二次调用lock时调用了wait阻塞,线程就卡在这里了,没办法释放锁,所以上面的方式只能按顺序进行加锁和解锁。
我们对自定义锁MyLock进行优化
public class MyLock implements Lock {
// 是否持有锁:默认为否
private boolean isHoldLock = false;
private Thread holdLockThread = null;
private int reentryCount = 0;
/**
* 同一时刻,能且仅能有一个线程获取到锁,其他线程只能等待线程释放锁之后才能获取到锁
* */
@Override
public synchronized void lock() {
if (isHoldLock && holdLockThread != Thread.currentThread()) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
holdLockThread = Thread.currentThread();
isHoldLock = true;
reentryCount++;
}
@Override
public synchronized void unlock() {
// 判断当前线程是否是持有锁的线程;如果是,重入次数减一
if (holdLockThread == Thread.currentThread()) {
reentryCount--;
if (reentryCount == 0) {
isHoldLock = false;
notify();
}
}
}
}
再运行ReentryDemo发现执行结果为:
methodA
methodB
是预期结果,线程不再阻塞。
Lock和synchronized的区别
- Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现,synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将 unLock()放到finally{} 中;
- synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
- Lock可以让等待锁的线程响应中断,线程可以中断去干别的事务,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
- 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
- Lock可以提高多个线程进行读操作的效率。