一、介绍
Jdk1.5以后,在java.util.concurrent.locks包下,有一组实现线程同步的接口和类,说到线程的同步,可能大家都会想到synchronized关键字,
这是java内置的关键字,用来处理线程同步的,但这个关键字有很多的缺陷,使用起来也不是很方便和直观,所以就出现了Lock,下面,我们
就来对比着讲解Lock。
通常我们在使用synchronized关键字的时候会遇到下面这些问题:
- 不可控性,无法做到随心的加锁和释放锁。
- 效率比较低下,比如我们现在并发的读两个文件,读与读之间是互不影响的,但如果给这个读的对象使用synchronized来实现同步的话,那么只要有一个线程进入了,那么其他的线程都要等待。
- 无法知道线程是否获取到了锁。
而上面synchronized的这些问题,Lock都可以很好的解决,并且jdk1.5以后,还提供了各种锁,例如读写锁,但有一点需要注意,使用 synchronized 关键时,无须手动释放锁,但使用 Lock 必须手动释放锁。下面我们就来学习一下Lock锁。
Lock是一个上层的接口,其原型如下,总共提供了6个方法:
public interface Lock {
// 用来获取锁,如果锁已经被其他线程获取,则一直等待,直到获取到锁
void lock();
// 该方法获取锁时,可以响应中断,比如现在有两个线程,一个已经获取到了锁,另一个线程调用这个方法正在等待锁,但是此刻又不想让这个线程一直在这死等,可以通过
调用线程的Thread.interrupted()方法,来中断线程的等待过程
void lockInterruptibly() throws InterruptedException;
// tryLock方法会返回bool值,该方法会尝试着获取锁,如果获取到锁,就返回true,如果没有获取到锁,就返回false,但是该方法会立刻返回,而不会一直等待
boolean tryLock();
// 这个方法和上面的tryLock差不多是一样的,只是会尝试指定的时间,如果在指定的时间内拿到了锁,则会返回true,如果在指定的时间内没有拿到锁,则会返回false
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 释放锁
void unlock();
// 实现线程通信,相当于wait和notify,后面会单独讲解
Condition newCondition();
}
二、ReentrantLock
java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。这就为 Lock 的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。 ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似轮询锁、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)
reentrant 锁意味着什么呢?
简单来说,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放。这模仿了 synchronized 的语义;如果线程进入由线程已经拥有的监控器保护的 synchronized 块,就允许线程继续进行,当线程退出第二个(或者后续) synchronized 块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个 synchronized 块时,才释放锁。
ReentrantLock构造器(有两个)
public ReentrantLock():默认是非公平策略锁
public ReentrantLock(Boolean):可选策略模式:true【公平策略】false【不公平策略】
非公平策略:由cpu调度指派(无法判断cpu调度顺序)无法保证等待线程获取锁的顺序
公平策略:等待锁的线程,按先到先得的方式公平用锁
三、lock,unlock的使用
lock.lock(); lock.unlock(); 包起来的代码,就和 synchronized 是一样的效果。
同一个lock对象锁是同一把;代码中serviceA ,serviceB 中的lock是同一把锁哦,谁先获取谁就有控制权。
由于不像 synchronized(异常情况会自动释放锁),lock【必须要手动释放锁】,所以一定要使用【try,finally】将执行业务包起来,避免异常情况下无法释放锁导致死锁。
lock.lock(); try { //需要执行的业务代码 } finally { lock.unlock(); }
package cn.thread.lock.reentrant;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class MyService {
//使用ReentrantLock
private Lock lock = new ReentrantLock();
public void serviceA() {
lock.lock();//serviceA ,serviceB 中的lock是同一把锁哦,谁先获取谁就有控制权。
try {
for (int i = 0; i < 3; i++) {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "----Ai=" + i);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void serviceB() {
lock.lock();
try {
for (int i = 0; i < 3; i++) {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "----Bi=" + i);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class ReentrantDome1 {
public static void main(String orgs[]) {
final MyService start = new MyService();
new Thread(new Runnable() {
public void run() {
start.serviceA();
}
}).start();
new Thread(new Runnable() {
public void run() {
start.serviceB();
}
}).start();
new Thread(new Runnable() {
public void run() {
start.serviceA();
}
}).start();
}
}
三、Condition接口实现线程通信 await()、signal()、signalAll
Condition
将Object
监视器方法(wait
、notify
和notifyAll
)分解成截然不同的对象,以便通过将这些对象与任意Lock
实现组合使用,为每个对象提供多个等待 set(wait-set)
Lock
替代了synchronized
方法和语句的使用。
Condition
替代了 Object 监视器方法的使用
- await 等价于 wait
- signal 等价于 notify
- signalAll 等价于 notifyAll
Condition
实例实质上被绑定到一个锁上。要为特定Lock
实例获得Condition
实例,请使用其new Condition()
方法。
案例一
作为一个示例,假定有一个绑定的缓冲区,它支持 put
和 take
方法。如果试图在空的缓冲区上执行 take
操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行 put
操作,则在有空间变得可用之前,线程将一直阻塞。我们喜欢在单独的等待 set 中保存 put
线程和 take
线程,这样就可以在缓冲区中的项或空间变得可用时利用最佳规划,一次只通知一个线程。可以使用两个 Condition
实例来做到这一点。
注意:不用 if 而是使用 while 进行判断,目的是为了防止虚假唤醒问题(官方说明写法)
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
案例二
A线程先运行,获取到了锁,但是进入await之后,交出锁权限,线程挂起;B线程获取到锁,B线程唤醒A线程,B线程运行完毕,A线程接着运行。
package cn.thread.lock.reentrant;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class MyService2 {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void serviceA() {
try {
lock.lock();
condition.await();//线程挂起,进入等待中,和wait一样。
System.out.println(Thread.currentThread().getName() + "----Ai=");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void serviceB() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "----Bi=");
condition.signal();//唤醒被await的线程
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class ReentrantDome2 {
public static void main(String orgs[]) {
final MyService2 start = new MyService2();
new Thread(new Runnable() {
public void run() {
start.serviceA();
}
}).start();
new Thread(new Runnable() {
public void run() {
start.serviceB();
}
}).start();
}
}