在Lock接口出现之前,java程序主要是靠synchronized关键字实现锁功能的,而JDK5之后,并发包中增加了lock接口,它提供了与synchronized一样的锁功能。虽然它失去了像synchronize隐式加锁解锁的便捷性,(Lock锁为显式),但是却拥有了锁获取和释放的可操作性,可中断的获取锁以及超时获取锁等多种synchronized关键字所不具备的同步特性(synchronized锁是互斥的)。
1. Lock
Lock–JDK 1.5基于Java语言实现的线程"锁"
可重入锁: 将持有锁的线程可以再次对锁的计数器 + 1
synchronized有可重入锁
// 在线程1中调用线程2
public synchronized void testA() {
//线程1
testB();
}
public synchronized void testB() {
//线程2
}
下面来看看Lock接口中定义了哪些方法
void lock();
// 获取锁void lockInterruptibly();
// 获取锁的过程能够响应中断boolean tryLock();
// 非阻塞式响应中断能立即返回,获取锁返回true反之为falseboolean tryLock(long time,TimeUnit unit);
// 超时获取锁,在超时内或未中断的情况下能获取锁Condition newCondition();
// 获取与lock绑定的等待通知组件,当前线程必须先获得了锁才能等待,等待会释放锁,再次获取到锁才能从等待中返回
2. Lock体系使用
Demo:
try {
//加锁
lock.lock();
}finally {
//解锁
lock.unlock();
}
独占锁:在任意时刻,只有一个线程拥有此锁
共享锁:在同一时刻,可以有多个线程拥有锁(读写锁是共享锁的一种,读锁共享,写锁独占)
java中实现线程“锁”的方式:
-
synchronized and Lock
-
synchronized 与 Lock的关系:
相同点:synchronized 与 Lock 都是对象锁,都支持可重入锁
不同点:
- Lock 可以实现 synchronized 不具备的特性:响应中断,支持超时,非阻塞的获取锁
- 实现公平锁
private Lock lock = new ReentrantLock(true);
- 读写锁:读锁共享,写锁读占
ReentrantReadWriteLock
- Lock体系的 Condition 队列可以有多个(区别于 synchronized 只有一个等待队列)
Lock的使用
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author: Mr.Q
* @Date: 2019-08-04 13:08
* @Description:
*/
class MyLock implements Runnable {
private Integer tickets = 50;
private Lock ticketsLock = new ReentrantLock(); //实现Lock的接口
@Override
public void run() {
while(tickets > 0) {
//需要对程序上锁
try {
//等同于 synchronized(this)
ticketsLock.lock();
if(this.tickets > 0) {
System.out.println(Thread.currentThread().getName() +
"还有" + this.tickets-- + "张票");
}
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
ticketsLock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
MyLock myLock = new MyLock();
new Thread(myLock,"黄牛A").start();
new Thread(myLock,"黄牛B").start();
Thread thread3 = new Thread(myLock,"黄牛C");
thread3.start();
}
}
可以看出 Lock 可以实现和 synchronized 同样的功能
死锁 产生条件:
- 互斥
- 占有且等待
- 不可抢占
- 循环等待
Lock解决死锁问题
破坏不可抢占(思路):
- 响应中断
lockInterruptibly
- 支持超时
boolean tryLock(long time, TimeUnit unit)
- 非阻塞式获取锁,线程若获取不到锁,线程直接退出
boolean tryLock()
响应中断 lockInterruptibly
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author: Mr.Q
* @Date: 2019-08-04 13:18
* @Description: 在获取锁时能够响应中断
*/
class TestLockInterrupt implements Runnable {
private Lock lock = new ReentrantLock();
@Override
public void run() {
try {
while (true) {
lock.lockInterruptibly();
}
} catch (InterruptedException e) {
System.err.println("线程"+Thread.currentThread().getName()+" 响应中断....");
return;
}finally {
lock.unlock();
}
}
}
public class LockInterrupt {
public static void main(String[] args) throws InterruptedException {
TestLockInterrupt testLock = new TestLockInterrupt();
Thread thread = new Thread(testLock,"Thread Q");
thread.start();
Thread.sleep(2000);
thread.interrupt();
}
}
在 while(true) { }
中响应了thread.interrupt();
支持超时 boolean tryLock(long time, TimeUnit unit)
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Lock;
/**
* @Author: Mr.Q
* @Date: 2019-08-04 13:45
* @Description:
*/
class ThreadLockTime implements Runnable {
private Lock lock = new ReentrantLock(); //实现 Lock的接口
@Override
public void run() {
fun();
}
private void fun() {
try {
if(lock.tryLock(1,TimeUnit.SECONDS)) {
System.out.println(Thread.currentThread().getName() + "获取锁成功!");
// sleep 2000ms,线程B获取不到锁
Thread.sleep(2000);
}else {
System.err.println(Thread.currentThread().getName() + "获取锁失败...");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class LockTime {
public static void main(String[] args) {
ThreadLockTime threadLockTime = new ThreadLockTime();
new Thread(threadLockTime,"ThreadA").start();
new Thread(threadLockTime,"ThreadB").start();
}
}
代码分析:
boolean tryLock(long timeout, TimeUnit unit)
如果在给定的等待时间内没有被另一个线程占用 ,并且当前线程尚未被保留,则获取该锁interrupted
A,B线程获取锁成功是随机的,看操作系统的调度. 我们以A线程获取锁成功为例
在run( )中调用fun( ),给定的等待时间为 if(lock.tryLock(1,TimeUnit.SECONDS))
1s,线程A先拿到锁之后,线程B想要获取锁时,必须得先sleep 2s;
休眠的时间大于给定的等待时间,所以线程B获取锁失败了!
3. Condition
Condition:Lock体系的线程通信方式,类比Object类的 wait,notify;可以进一部提高效率,减少线程阻塞与唤醒带来的开销
await()
: 释放Lock锁,将线程置入等待队列阻塞
signal()
: 唤醒一个处于等待状态的线程
signalAll()
: 唤醒所有线程
类比 wait() 和 notify( )
获取一个Lock锁的Condition队列:
Lock.newCondition
: 每当调用一次,就产生一个新的Condition
4. 到底使用 synchronized 还是 Lock?
- 若无特殊的应用场景,推荐使用 synchronized,其使用方便(隐式的加减锁),并且由于 synchronized是 JVM层面的实现,在之后的 JDK还有优化
- 若要使用 公平锁,读写锁,超时锁等特殊场景,才会考虑使用 Lock