ReentrantLock类的使用
在java多线程中,可以使用synchronized关键字来实现线程之间的同步互斥,但在JDK1.5中新增了ReentrantLock类也能达到相同的效果,并且在扩展功能上也更加强大,比如具有嗅探锁定,多路分支通知等功能,而且在使用上也比synchronized更加的灵活。
首先我们先通过一个简单的例子来了解ReentrantLock类的使用。
MyService.java的代码如下:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyService {
private Lock lock = new ReentrantLock();
public void testMethord(){
lock.lock();
for(int i=0;i<10;i++){
System.out.println("ThreadCurrentName is "+Thread.currentThread().getName()+" and i = "+(i+1));
}
lock.unlock();
}
}
线程类的代码
public class MyThread extends Thread{
private MyService service;
public MyThread(MyService myService){
this.service = myService;
}
public void run(){
service.testMethord();
}
}
运行类的代码
public class runTest {
public static void main(String[] agrs) {
MyService service = new MyService();
MyThread a1 = new MyThread(service);
MyThread a2 = new MyThread(service);
MyThread a3 = new MyThread(service);
MyThread a4 = new MyThread(service);
a1.start();
a2.start();
a3.start();
a4.start();
}
}
运行结果如图:
从运行的结果来看,当前线程打印完毕之后将锁进行释放,其他的线程才可以继续打印,线程打印的数据是分组打印,因为当前线程已经持有锁,但线程之间打印的顺序是随机的。
使用Condition实现等待/通知
关键字synchronized和wait()和notify()方法相结合可以实现等待,通知模式,类ReentrantLock也可以实现相同的功能,但需要借助Condition对象。Condition类是在JDK5中出现的技术,使用他有更好的灵活性,比如可以实现多路通知功能。
在使用notify方法进行通知时,被通知的线程却是由JVM随机选择的。但是用ReenTranLock结合Condition类可以实现前面介绍过的“选择性通知” ,这个功能是非常重要的,而且在Confition类是默认提供的。
而synchronized就相当于整个Lock对象中只有一个单一的Condition对象,所有的线程都注册在它一个对象的身上。线程开始notifyAll时,需要通知所有的WAITNG线程,没有选择权,会出现相当大的效率问题。
Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。
条件(也称为条件队列 或条件变量)为线程提供了一个含义,以便在某个状态条件现在可能为 true 的另一个线程通知它之前,一直挂起该线程(即让其“等待”)。因为访问此共享状态信息发生在不同的线程中,所以它必须受保护,因此要将某种形式的锁与该条件相关联。等待提供一个条件的主要属性是:以原子方式 释放相关的锁,并挂起当前线程,就像 Object.wait 做的那样。
Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得 Condition 实例,请使用其 newCondition() 方法。
作为一个示例,假定有一个绑定的缓冲区,它支持 put 和 take 方法。如果试图在空的缓冲区上执行 take 操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行 put 操作,则在有空间变得可用之前,线程将一直阻塞。我们喜欢在单独的等待 set 中保存 put 线程和 take 线程,这样就可以在缓冲区中的项或空间变得可用时利用最佳规划,一次只通知一个线程。可以使用两个 Condition 实例来做到这一点。
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();
}
}
}
下面通过一个例子来学习Condition类的学习:
MyService文件代码如下:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyService {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void waitMethord() {
try {
lock.lock();
System.out.println("wait A>>");
condition.await();
System.out.println("wait B>>");
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
lock.unlock();
System.out.println("lock is releast");
}
}
public void signalMethord(){
try {
lock.lock();
System.out.println("singal A>>==========");
condition.signal();
System.out.println("singal B>>==========");
} catch (Exception e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}
}
线程类:
public class ThreadA extends Thread{
private MyService service;
public ThreadA(MyService servie){
this.service = servie;
}
public void run(){
service.waitMethord();
}
}
//线程B
public class ThreadB extends Thread{
private MyService service;
public ThreadB(MyService servie){
this.service = servie;
}
public void run(){
service.signalMethord();
}
}
运行类:
public class runTest {
public static void main(String[] agrs) {
MyService servicer = new MyService();
ThreadA a = new ThreadA(servicer);
ThreadB b = new ThreadB(servicer);
a.start();
b.start();
}
}
运行结果如图:
通过本程序,成功通过Condition类实现等待/通知模式。
- Object类中的wait()方法相当于Condition类中的await()方法。
- Object类中的wait(long)方法相当于Condition类中的await(long,TimeUnit)方法。
- Object类中的notify()方法相当于Condition类中的signal()方法。
- Object类中的notifyAll()方法相当于Condition类中的sigalAll()方法。
通过多个Condition实现通知部分线程
不多说,直接上代码:
MyService.java文件类代码
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyService {
private Lock lock = new ReentrantLock();
public Condition conditionA = lock.newCondition();
public Condition conditionB = lock.newCondition();
public void awaitA() {
try {
lock.lock();
System.out.println("AAAAAAAAAAAA begin await time is "
+ System.currentTimeMillis() + " ThreadName = "
+ Thread.currentThread().getName());
conditionA.await();
System.out.println("AAAAAAAAAAAA end await time is "
+ System.currentTimeMillis() + " ThreadName = "
+ Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}
public void awaitB() {
try {
lock.lock();
System.out.println("BBBBBBBBBBBBBB begin await time is "
+ System.currentTimeMillis() + " ThreadName = "
+ Thread.currentThread().getName());
conditionB.await();
System.out.println("BBBBBBBBBBBBB end await time is "
+ System.currentTimeMillis() + " ThreadName = "
+ Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}
public void singalAll_A(){
try{
lock.lock();
System.out.println("singal AAAAAAAAAAAA begin await time is "
+ System.currentTimeMillis() + " ThreadName = "
+ Thread.currentThread().getName());
conditionA.signalAll();
}finally{
lock.unlock();
}
}
public void singalAll_B(){
try{
lock.lock();
System.out.println("singal BBBBBBBBBB begin await time is "
+ System.currentTimeMillis() + " ThreadName = "
+ Thread.currentThread().getName());
conditionB.signalAll();
}finally{
lock.unlock();
}
}
}
线程类
//线程A
public class ThreadA extends Thread{
private MyService myService;
public ThreadA(MyService myService){
this.myService = myService;
}
public void run(){
for(int i=0;i<Integer.MAX_VALUE;i++){
myService.awaitA();
}
}
}
//线程B
public class ThreadB extends Thread{
private MyService myService;
public ThreadB(MyService myService){
this.myService = myService;
}
public void run(){
myService.awaitB();
}
}
运行类:
public class runTest {
public static void main(String [] agrs) throws InterruptedException{
MyService myService = new MyService();
ThreadA a = new ThreadA(myService);
ThreadB b = new ThreadB(myService);
a.setName("ThreadA");
b.setName("ThreadB");
a.start();
b.start();
Thread.sleep(3000);
myService.singalAll_A();
}
}
运行结果如图:
若在runTest最后添加一句:
myService.singalAll_B();
则最后的结果如图,且运行按键为灰色,代表没有线程处于阻塞状态
这个程序应该还是比较好理解的,这里就不多做解释说明了。
实现生产者/消费者模式:一对一交替打印
这里也直接上代码
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class MyService {
private ReentrantLock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private boolean hasValue = false;
public void set() {
try {
lock.lock();
while (hasValue == true) {
condition.await();
}
System.out.println("###");
hasValue = true;
condition.signal();
get();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void get() {
try {
lock.lock();
while (hasValue == false) {
condition.await();
}
System.out.println("***");
hasValue = false;
condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
//线程A
public class ThreadA extends Thread{
private MyService myService;
public ThreadA(MyService myService){
this.myService = myService;
}
public void run(){
for(int i=0;i<Integer.MAX_VALUE;i++){
myService.set();
}
}
}
//线程B
public class ThreadB extends Thread {
private MyService myService;
public ThreadB(MyService myService) {
this.myService = myService;
}
public void run() {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
myService.get();
}
}
}
//运行类
public class runTest {
public static void main(String [] args){
MyService myServie = new MyService();
ThreadA a = new ThreadA(myServie);
a.start();
ThreadA b = new ThreadA(myServie);
b.start();
}
}
运行结果如图:两个方法依次执行了。
[图片上传中。。。(5)]
java.util.concurrent.locks 中的相关方法
软件包 java.util.concurrent.locks 的描述
为锁和等待条件提供一个框架的接口和类,它不同于内置同步和监视器。该框架允许更灵活地使用锁和条件,但以更难用的语法为代价。
Lock 接口支持那些语义不同(重入、公平等)的锁规则,可以在非阻塞式结构的上下文(包括 hand-over-hand 和锁重排算法)中使用这些规则。主要的实现是 ReentrantLock。
ReadWriteLock 接口以类似方式定义了一些读取者可以共享而写入者独占的锁。此包只提供了一个实现,即 ReentrantReadWriteLock,因为它适用于大部分的标准用法上下文。但程序员可以创建自己的、适用于非标准要求的实现。
Condition 接口描述了可能会与锁有关联的条件变量。这些变量在用法上与使用 Object.wait 访问的隐式监视器类似,但提供了更强大的功能。需要特别指出的是,单个 Lock 可能与多个 Condition 对象关联。为了避免兼容性问题,Condition 方法的名称与对应的 Object 版本中的不同。
AbstractQueuedSynchronizer 类是一个非常有用的超类,可用来定义锁以及依赖于排队阻塞线程的其他同步器。 AbstractQueuedLongSynchronizer 类提供相同的功能但扩展了对同步状态的 64 位的支持。两者都扩展了类 AbstractOwnableSynchronizer(一个帮助记录当前保持独占同步的线程的简单类)。LockSupport 类提供了更低级别的阻塞和解除阻塞支持,这对那些实现自己的定制锁类的开发人员很有用。
下面就其中比较重要的几个方法和知识点进行介绍。
方法名 | 作用 | 备注说明 | 用法示例 |
---|---|---|---|
new ReentrantLock(boolean) | 创建公平锁与非公平锁 | 公平锁与非公平锁的含义与区别参见注释1 | private ReentrantLock lock = new ReentrantLock(true); |
int getHoldCount() | 查询当前线程保持此锁定的个数,也就是查询当前调用lock方法的次数 | 通常只用于测试和调试。 | lock.getHoldCount(); |
int getQueuedThreads() | 返回正在等待获取次锁定的线程估计数 | 此方法用于监视系统状态,不用于同步控制。 | lock.getQueueLength(); |
int getWaitQueueLength(Condition) | 返回等待与此锁相关的给定条件的线程估计数。 | 注意,因为随时可能发生超时和中断,所以只能将估计值作为实际等待线程数的上边界。此方法用于监视系统状态,不用于同步控制。 | private Condition condition =lock.newCondition(); lock.getWaitQueueLength(condition); |
final boolean hasQueuedThread(Thread) | 查询制定的线程是否正待等待获取次锁定 | 注意,因为随时可能发生取消,所以返回 true 并不保证此线程将获取此锁。此方法主要用于监视系统状态。 | lock.hasQueuedThread(thread); |
final boolean hasQueuedThreads() | 查询是否有些线程正在等待获取此锁。 | 因为随时可能发生取消,所以返回 true 并不保证有其他线程将获取此锁。此方法主要用于监视系统状态。 | lock.hasQueuedThread(); |
boolean hasWaiters(Condition) | 查询是否有线程正待等待与此锁定有关的Condition条件 | 注意,因为随时可能发生超时和中断,所以返回 true 并不保证将来某个 signal 将唤醒线程。此方法主要用于监视系统状态。 | lock.hasWaiters(condition); |
final boolean isFair() | 判断是不是公平锁 | 如果此锁的公平设置为 true,则返回 true。 | lock.isFair(); |
boolean isLocked() | 查询此锁定是否由任意线程保持。 | 此方法用于监视系统状态,不用于同步控制。 如果任意线程保持此锁,则返回 true;否则返回 false | lock.isLocked(); |
boolean isHeldByCurrentThread() | 查询当前线程是否保持此锁。 | 如果当前线程保持此锁,则返回 true;否则返回 false | lock.isHeldByCurrentThread(); |
void lockInterruptibly() throws InterruptedException | 如果当前线程未被中断,则获取锁定,如果已经被中断则出现异常 | 在此实现中,因为此方法是一个显式中断点,所以要优先考虑响应中断,而不是响应锁的普通获取或重入获取。 (具体参见JDK api) | lock.lockInterruptibly(); |
boolean tryLock() | 仅在调用时锁未被另一个线程保持的情况下,才获取该锁。 | 如果该锁没有被另一个线程保持,并且立即返回 true 值,则将锁的保持计数设置为 1。即使已将此锁设置为使用公平排序策略,但是调用 tryLock() 仍将 立即获取锁(如果有可用的),而不管其他线程当前是否正在等待该锁。 | lock.tryLock(); |
boolean tryLock(long ,TimeUnit ) throws InterruptedException | 如果锁定在给定等待时间内有没有被另一个线程保持,且当前线程未被中断,则获取该锁定。 | timeout - 等待锁的时间 unit - timeout 参数的时间单位 | lock.tryLock(3,TimeUnit.SECONDS); |
void awaitUninterruptibly() | 设置当前线程在接到信号之前一直处于等待状态。 | 假定调用此方法时,当前线程保持了与此 Condition 有关联的锁。这取决于确定是否为这种情况以及不是时,如何对此作出响应的实现。通常,将抛出一个异常(比如 IllegalMonitorStateException)并且该实现必须对此进行记录。 | condition.awaitUninterruptibly(); |
void awaitUntil(Date) throws InterruptedException | 造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。 | 假定调用此方法时,当前线程保持了与此 Condition 有关联的锁。这取决于确定是否为这种情况以及不是时,如何对此作出响应的实现。通常,将抛出一个异常(比如 IllegalMonitorStateException)并且该实现必须对此进行记录。 与响应某个信号而返回的普通方法相比,或者与指示是否到达指定最终期限相比,实现可能更喜欢响应某个中断。在任意一种情况下,实现必须确保信号被重定向到另一个等待线程(如果有的话)。 | Calendar calendarRef = Calendar.getInstance(); condition.awaitUntil(calendarRef.getTime); |
注释1:公平锁与非公平锁:锁Lock分为“公平锁”和“非公平锁”。公平锁表示线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得的FIFO顺序,而非公平锁就是一种获取锁的抢占机制,是随机获取的,和公平锁不一样的就是先来的不一定先得到锁,这个方式可能造成某些线程一直拿不到锁。Condition某人使用的是非公共锁。
使用ReentrantReadWriterLock类
类ReentrantReadWriterLock类具有完全互斥排他小效果,即同一时间只有一个线程在执行ReetrantLock()方法后面的任务。这样做虽然保证了实例变量的线程安全性,但效率却是非常低下的。所以在JDK中提供可一种读写锁ReentrantReadWriterLock类,使用它可以加快运行效率,在某些不需要操作实例变量的方法中,完全可以使用读写锁ReentranReadWriterLock来提升该方法的代码运行效率。
读写锁表示也有两个锁,一个是读操作相关的锁,也称为共享锁,另一个是写操作相关的锁,也叫排他锁。也就是多个读锁之间不互斥,读锁与写锁互斥,写锁与写锁互斥。而进行写入操作的Thread只有获取写锁后才能进行写入操作,即多个Thread可以同时进行读取操作,但是同一时刻只允许一个Thread进行写入操作。
下面以一个实例来介绍如何使用这两种锁。
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Service {
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void read() {
try {
lock.readLock().lock();
System.out.println(Thread.currentThread().getName() + "获取读锁 时间为:"
+ System.currentTimeMillis());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}
}
public void write() {
try {
try {
lock.writeLock().lock();
System.out.println(Thread.currentThread().getName()
+ "获取写锁 时间为:" + System.currentTimeMillis());
Thread.sleep(1000);
} finally {
lock.writeLock().unlock();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
线程类:
//线程A
public class ThreadA extends Thread {
private Service service ;
public ThreadA(Service servie){
this.service = servie;
}
public void run(){
service.read();
}
}
//线程B
public class ThreadB extends Thread {
private Service service ;
public ThreadB(Service servier){
this.service = servier;
}
public void run(){
service.read();
}
}
//线程C
public class ThreadC extends Thread {
private Service service ;
public ThreadC(Service service){
this.service = service;
}
public void run(){
service.write();
}
}
//线程D
public class ThreadD extends Thread{
private Service service ;
public ThreadD(Service service){
this.service = service;
}
public void run(){
service.write();
}
}
运行类:
public class runTest {
public static void main(String [] agrs) throws InterruptedException{
Service service = new Service();
ThreadA a = new ThreadA(service);
ThreadB b = new ThreadB(service);
ThreadC c = new ThreadC(service);
ThreadD d = new ThreadD(service);
a.start();
a.setName("a");
b.setName("b");
c.setName("c");
d.setName("d");
b.start();
Thread.sleep(1000);
System.out.println("---------------------");
c.start();
d.start();
}
}
运行效果如图:
可以明显的看出来,读读是在同一时刻运行的,而写操作一次只能运行一个线程。至于读写之间的互斥,读者可以自行进行验证。这里就不做介绍了。