在JDK5里面,提供了一个Lock接口。该接口通过底层框架的形式为设计更面向对象、可更加细粒度控制线程代码、更灵活控制线程通信提供了基础。实现Lock接口且使用得比较多的是可重入锁(ReentrantLock)以及读写锁(ReentrantReadWriteLock)。

1. ReentrantLock

  在Java多线程(二) 多线程的锁机制 里面,已经总结过通过使用Synchronized关键字实现线程内的方法锁定。但使用Synchronized关键字有一些局限性,上锁和释放锁是由JVM决定的,用户没法上锁和释放进行控制。那么问题就来了:假如有一个线程业务类管理某一全局变量的读和写。对于每条线程,在读的时候数据是共享的可以让多个线程同时去读。但有某个线程在对该全局变量进行写的时候,其他的线程都不能够对变量进行读或者写(对应数据库内的读共享写互斥)。可能会有如下伪代码:

 package com.scl.thread.lock;

 public class MyCounter
{
public int count; public int readCount()
{
return this.count;
} public void writeCount()
{
synchronized(this)
{
count++;
}
}
}

  尽管对写操作进行了空值,但是在写的时候,线程还是能够进行读操作!由此,JDK5并发库内提供了Lock接口。程序员可以通过实现Lock接口对代码块进行更灵活的锁控制.

JDK5通过使用AbstractQueuedSynchronizer(简写为AQS)抽象类把Lock接口的功能实现了一大部分功能,如果程序员需要编写一套跟有自身逻辑的"锁"时,可以简单地通过实现public boolean tryAcquire(int acquire) 及 public boolean tryRelease(int releases) 进行加锁及释放锁功能。AQS为整个并发内容的核心框架,类似synchronized的锁(ReentrantLock :可重入锁)就是使用了AQS框架进行构建。ReentrantLock提供了一个可中断、拥有并发竞争机制[指线程对锁的竞争方式:公平竞争或不公平竞争]的方式,该部分的内容的源码分析可以查看: ReentrantLock 实现原理深入探究

正如ReentrantLock跟Synchronized关键字所使用的功能基本一样,而且Synchronized还能自己释放锁,那什么时候使用ReentrantLock?

  ① 在中断线程的时候,可以使用ReentrantLock进行控制

    如线程1有一个耗时很大的任务在执行,执行时线程2必须进行等待。当线程1执行的任务时间实在太长了,线程2放弃等待进行线程后续的操作。该情况下如果使用Synchronized,只能通过抛出异常的形式进行异常操作。

  ② 多条件变量通讯

    如有3条线程,线程1完成任务后通知线程2执行,线程2执行完业务逻辑以后通知线程3执行,线程3执行完通知线程1继续执行。用Synchronized关键字很难处理这种问题。用Lock却可以很好的处理这些内容。当然,线程1 、2、3 同样地可以换由一个线程组去执行这些任务。  

1.1  可中断的线程控制

1.1.1  Java的线程中断机制

  Java中断线程可以通过实例方法: stop 或 interrupt 进行线程中断,两者有什么区别?先查看以下两段代码及运行结果。

 package com.scl.thread.interrupt;

 public class TestInterrupt
{
// 各线程可见的线程状态标志位
public static volatile boolean isStop = false; public static void main(String[] args) throws InterruptedException
{
// 创建三条线程,线程1使用stop方法中断,线程2使用interrupt方法中断,线程3与线程2比较使用了interrupt后是否因中断退出
Thread th1 = new Thread(new SubThread1(), "SubThread1");
Thread th2 = new Thread(new SubThread2(), "SubThread2");
Thread th3 = new Thread(new SubThread3(), "SubThread3"); System.out.println("==============subThread1 code block result==============");
System.out.println("Main Thread call subThread1 to start");
th1.start();
Thread.sleep(3000);
System.out.println("Main Thread start to stop subThread1");
th1.stop();
System.out.println("subThread1 was stopped by Main Thread");
// 等待子线程进行stop,让子线程有充分时间处理相关业务
Thread.sleep(20);
System.out.println("===================================================");
Thread.sleep(20); System.out.println("==============subThread2 code block result==============");
System.out.println("Main Thread call subThread2 to start");
th2.start();
Thread.sleep(3000);
System.out.println("Main Thread start to interrupt subThread2");
// 设置标志位,令子线程2可以按顺序退出
isStop = true;
th2.interrupt();
// 等待子线程进行interrupt,让子线程有充分时间处理相关业务
Thread.sleep(20);
System.out.println(" subThread2 was interruptted by Main Thread");
System.out.println("===================================================");
Thread.sleep(20); System.out.println("==============subThread3 code block result==============");
System.out.println("Main Thread call subThread3 to start");
th3.start();
Thread.sleep(3000);
System.out.println("Main Thread start to interrupt subThread3");
th2.interrupt();
// 等待子线程进行interrupt,让子线程有充分时间处理相关业务
Thread.sleep(20);
System.out.println("subThread3 was interrupted by Main Thread");
System.out.println("===================================================");
Thread.sleep(20); System.out.println("Main Thread end");
}
} class SubThread1 implements Runnable
{
@Override
public void run()
{
while (!TestInterrupt.isStop)
{
try
{
// 子线程1进行睡眠
Thread.sleep(2000);
}
catch (InterruptedException e)
{
e.printStackTrace();
} System.out.println(Thread.currentThread().getName() + " is running...");
}
// 调用stop方法,该语句不会被执行,因为线程整个退出了
System.out.println(Thread.currentThread().getName() + " is ready to cancle");
}
} class SubThread2 implements Runnable
{
@Override
public void run()
{
while (!TestInterrupt.isStop)
{
try
{
// 子线程1进行睡眠
Thread.sleep(200);
}
catch (InterruptedException e)
{
e.printStackTrace();
} System.out.println(Thread.currentThread().getName() + " is running...");
}
// 使用interrupt方法,在发现线程2被阻塞或休眠(sleep)的情况下,会收到一个interrupt的异常。但不会终止线程,仅设置线程是否可以中断的标志位
System.out.println(Thread.currentThread().getName() + " is ready to cancle");
}
} // 调用interrupt方法,对比子线程2,发现使用interrupt方法根本没有中断整个线程,设置后线程也没有进行退出。一直运行
class SubThread3 implements Runnable
{
@Override
public void run()
{
// 使用true代替标志位,判断调用interrupt方法后是否正常中断线程
while (true)
{
try
{
Thread.sleep(2000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " is running...");
}
}
}

stop方法及interrupt方法对比

 ==============subThread1 code block result==============
Main Thread call subThread1 to start
SubThread1 is running...
Main Thread start to stop subThread1
subThread1 was stopped by Main Thread
===================================================
==============subThread2 code block result==============
Main Thread call subThread2 to start
SubThread2 is running...
SubThread2 is running...
SubThread2 is running...
SubThread2 is running...
SubThread2 is running...
SubThread2 is running...
SubThread2 is running...
SubThread2 is running...
SubThread2 is running...
SubThread2 is running...
SubThread2 is running...
SubThread2 is running...
SubThread2 is running...
SubThread2 is running...
Main Thread start to interrupt subThread2
SubThread2 is running...
SubThread2 is ready to cancle
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.scl.thread.interrupt.SubThread2.run(TestInterrupt.java:91)
at java.lang.Thread.run(Thread.java:744)
subThread2 was interruptted by Main Thread
===================================================
==============subThread3 code block result==============
Main Thread call subThread3 to start
SubThread3 is running...
Main Thread start to interrupt subThread3
subThread3 was interrupted by Main Thread
===================================================
Main Thread end
SubThread3 is running...
SubThread3 is running...

运行结果

  从代码运行结果上面,可以看到两个函数的差异:stop把线程给结束了,而interrupt方法没有结束线程,因此两者总结如下:

① 在线程实例调用stop 方法后把线程给终结了,在子线程1内while以外的代码块将不会被执行

② 在线程实例调用interrupt 方法后,线程没有被终结且该方法通过线程间协作的关系,可以有序地退出线程。且实例线程在被中断后,若线程被阻塞或者休眠的情况下,将收到一个InterruptedException。

③ 子线程3的运行结果充分证实了线程实例调用interrupt方法根本没有把线程给中断,只是把线程的标志状态进行更改。让线程实例在适当的时机进行退出。

由此可见public void interrupt( )并非马上对线程进行中断(强行结束线程),而是通过协作的方法把线程的状态设置为可中断。告知阻塞中的线程在特定的时刻可以对进行终结。同时,Java提供了两个检验线程中断方法① 实例方法 public boolean isInterrupted() ②静态方法 public static boolean interrupted();

两个方法的区别是:静态方法会去清理线程状态信息。实例方法不会清理状态标志。即当线程实例调用interrupt 的前提下,若再调用静态方法,第一次会返回true,后面全返回false;若在前提下,调用实例方法isInterrupt,如果线程正常退出,会一直返回false [判断线程Thread对象是否已经是终止状态,与线程状态无关]。静态方法偏向于判断线程状态。而实例方法更关心线程是否存活。

  需要特别注意的是当线程调用interrupt方法时,假如线程在等待锁或者被休眠了。中断状态会被设置为false。JDK的API里面明确指出了这一点。

下面开始查看Lock与synchronized关键字与ReentrantLock的一些区别。

1.1.2 ReentrantLock对线程中断的控制

首先,单纯地使用synchronized关键字不能进行锁中断控制. 在synchronized关键字控制的代码块内,不会因为线程中断而做出相关处理。

先查看使用synchronized关键字在处理线程中断时的结果。

业务逻辑主要为:开辟两条线程,一条线程对文件进行读操作,另一条线程对文件进行写操作。写操作内容需要时间较长,且先执行。读操作后执行,若读线程等待超过4秒。让读线程中断,进行格式化文件。

① 使用接口,区分使用synchronized关键字及Lock方式控制线程中断的业务逻辑

 package com.scl.thread.interrupt;

 //文件读写接口,使用Synchronized关键字控制线程中断以及使用Lock控制线程中断都实现该接口
public interface IFileHandler
{
boolean isGetReadLock = false; void read(); void write(); void formatFile();
}

控制文件接口

② 在synchronized关键字控制代码块的前提下,对线程进行中断的业务逻辑代码。

 package com.scl.thread.interrupt;

 public class SyncFileHandler implements IFileHandler
{
private volatile boolean isGetReadLock = false; public boolean isGetReadLock()
{
return isGetReadLock;
} public void read()
{
synchronized (FileHandlerByThreads.class.getClass())
{
System.out.println(Thread.currentThread().getName() + " start");
// 能进来则设置变量标志位
isGetReadLock = true;
}
} // 模拟运行时间比较久的写操作
public void write()
{
try
{
synchronized (FileHandlerByThreads.class.getClass())
{
System.out.println(Thread.currentThread().getName() + " start");
long startTime = System.currentTimeMillis();
// 模拟一个耗时较长的操作
for (;;)
{
if (System.currentTimeMillis() - startTime > Integer.MAX_VALUE)
{
break;
}
}
} System.out.println("Writer has writered down everything! bravo");
}
catch (Exception e)
{
e.printStackTrace();
} } public void formatFile()
{
System.out.println("begin to format the file");
// format the file
}
}

synchronized控制下的线程中断

③ 客户端测试代码

 package com.scl.thread.interrupt;

 public class TestLockInterruptibly
{
public static void main(String[] args) throws Exception
{
// 1. 根据lock控制中断
// FileHandlerByThreads fileControl = new FileHandlerByThreads();
// Thread readthr = new Thread(new ReadThread(fileControl), "reader");
// Thread writethr = new Thread(new WriteThread(fileControl), "writer"); // 2. 使用synchronized关键字控制中断线程
SyncFileHandler sync = new SyncFileHandler(); Thread readthr = new Thread(new ReadThread(sync), "reader");
Thread writethr = new Thread(new WriteThread(sync), "writer");
writethr.start();
readthr.start(); long startTime = System.currentTimeMillis();
// 循环判是否有线程获取到了读锁断
while (!sync.isGetReadLock())
{
long endTime = System.currentTimeMillis();
// 如果4秒后读线程仍然没有等到读锁,离开等待
if (endTime - startTime > 4000)
{
readthr.interrupt();
System.out.println("4 seconds have passed,try to interrupt reader Thread");
break;
}
} }
} class ReadThread implements Runnable
{
private IFileHandler fileControl; public ReadThread(IFileHandler fileControl)
{
this.fileControl = fileControl;
} @Override
public void run()
{
fileControl.read();
// 测试单纯使用synchronized关键字控制线程中断
System.out.println("reader thread end");
fileControl.formatFile();
}
} class WriteThread implements Runnable
{
private IFileHandler fileControl; public WriteThread(IFileHandler fileControl)
{
this.fileControl = fileControl;
} @Override
public void run()
{
fileControl.write();
}
}

客户端测试代码

代码运行结果:线程未中断,控制台输出如下

 writer start
4 seconds have passed,try to interrupt reader Thread

synchronized代码运行结果

如上面的结果,我们期望的是在读线程在运行4秒后能够被中断,且去运行格式化代码的任务。但是在读线程在调用interrupt方法后,读方法后面的代码并没有执行。反而是一直等待。控制台并没有输出"reader thread end",以及格式化代码的操作。由此可见synchronized关键字不会去响应线程中断。

查看了大部分博客后,发现大家写的都是synchronized并不响应中断。但使用synchronized是否不能完成可中断线程的响应呢?

要接收到中断信息,无非有两种方法 ①等待锁(使用wait、join等方法) ②进入休眠。这两个做法都需要使用循环,让程序等待。第一种方法完全不可行,我现在就是想要什么时候能够获取到锁,JDK通过synchronized没有提供方法让程序员知道:"我的代码获取到锁了吗"这个条件,其次等待对象wait方法,需要在synchrnized里面。synchronized (FileHandlerByThreads.class.getClass())这个条件本来就进不去,更别谈里面的wait方法了。第二种方法,让程序进入休眠。因此有以下代码

     public void read()
{
try
{
System.out.println(Thread.currentThread().getName() + " start");
while (true)
{
Thread.sleep(100);
if (Thread.currentThread().isInterrupted())
{
break;
}
}
synchronized (FileHandlerByThreads.class.getClass())
{
System.out.println(Thread.currentThread().getName() + " start");
} }
catch (InterruptedException e)
{
e.printStackTrace();
System.out.println("reader Thread leave the file and going to format the file");
}
finally
{
// lock.unlock();
} }

未卜先知型 sleep

这样写,终于能够获取到interrupt发送过来的信息,并且捕获到异常了。但是,read方法一直都在休眠。这做法不是未卜先知了吗,因为你都知道了read方法肯定是得不到锁的,不断地在休眠。

因此,使用synchronized真的没有方法合理地中断线程的响应。

下面使用ReentrantLock实现可中断线程控制,过程非常简单。把程序稍修改一下就可以了

 package com.scl.thread.interrupt;

 import java.util.concurrent.locks.ReentrantLock;

 public class FileHandlerByThreads implements IFileHandler
{ private volatile boolean isGetReadLock = false;
private ReentrantLock lock = new ReentrantLock(); public boolean isGetReadLock()
{
return isGetReadLock;
} public void read()
{ try
{
// 等待20毫秒再进行后续操作,防止主线程操作过快
Thread.sleep(50);
// 使用reentrantlock
lock.lockInterruptibly();
System.out.println(Thread.currentThread().getName() + " start");
isGetReadLock = true;
}
catch (InterruptedException e)
{
e.printStackTrace();
System.out.println("reader Thread leave the file and going to format the file");
}
// 不能在此处进行锁释放,因为被阻塞的线程可能根本没有获取到锁,若调用unlock方法会抛出IllegalMonitorStateException异常
// finally
// {
// lock.unlock();
// } } // 模拟运行时间比较久的写操作
public void write()
{
try
{ // 1.使用lock实现写锁定
// 等待20毫秒再进行后续操作,防止主线程操作过快
Thread.sleep(20);
lock.lock();
System.out.println(Thread.currentThread().getName() + " start");
long startTime = System.currentTimeMillis();
// 模拟一个耗时较长的操作
for (;;)
{
if (System.currentTimeMillis() - startTime > Integer.MAX_VALUE)
{
break;
}
} System.out.println("Writer has writered down everything! bravo");
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
lock.unlock();
}
} public void formatFile()
{
System.out.println("begin to format the file");
// format the file
}
}

Reentrantlock实现可中断线程

 package com.scl.thread.interrupt;

 public class TestLockInterruptibly
{
public static void main(String[] args) throws Exception
{
// 1. 根据lock控制中断
FileHandlerByThreads fileControl = new FileHandlerByThreads();
Thread readthr = new Thread(new ReadThread(fileControl), "reader");
Thread writethr = new Thread(new WriteThread(fileControl), "writer"); // 2. 使用synchronized关键字控制中断线程
// SyncFileHandler sync = new SyncFileHandler();
// Thread readthr = new Thread(new ReadThread(sync), "reader");
// Thread writethr = new Thread(new WriteThread(sync), "writer"); writethr.start();
readthr.start(); long startTime = System.currentTimeMillis();
// 循环判是否有线程获取到了读锁断
while (!fileControl.isGetReadLock())
{
long endTime = System.currentTimeMillis();
// 如果4秒后读线程仍然没有等到读锁,离开等待
if (endTime - startTime > 4000)
{
readthr.interrupt();
System.out.println("4 seconds have passed,try to interrupt reader Thread");
break;
}
} }
} class ReadThread implements Runnable
{
private IFileHandler fileControl; public ReadThread(IFileHandler fileControl)
{
this.fileControl = fileControl;
} @Override
public void run()
{
try
{
fileControl.read();
}
catch (InterruptedException e)
{
// 测试单纯使用synchronized关键字控制线程中断
System.out.println("reader thread end");
fileControl.formatFile();
}
}
} class WriteThread implements Runnable
{
private IFileHandler fileControl; public WriteThread(IFileHandler fileControl)
{
this.fileControl = fileControl;
} @Override
public void run()
{
fileControl.write();
}
}

客户端测试代码

 writer start
4 seconds have passed,try to interrupt reader Thread
reader Thread leave the file and going to format the file
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:896)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1221)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:340)
at com.scl.thread.interrupt.FileHandlerByThreads.read(FileHandlerByThreads.java:24)
at com.scl.thread.interrupt.ReadThread.run(TestLockInterruptibly.java:51)
at java.lang.Thread.run(Thread.java:744)

运行结果

需要注意的是FileHandlerByThreads这个类里面的read方法,不能用finally去对lock给解锁。因为被阻塞的读线程基本不可能获取到锁,如果再去释放锁的话会抛出一个java.lang.IllegalMonitorStateException异常。

在总结这随笔之前,本人也有好几个疑问:

① Reentrantlock锁的是什么对象?

synchronized关键字的实现中,本人通过使用锁静态对象的方法把代码块给控制了,但是Reentrantlock 的lock实例根本没有指定任何锁定对象,那锁定的到底是什么。个人认为Reentrantlock根本没有锁定任何东西,因为这个框架底层都是基于CAS去实现的,在底层代码里面也没有发现任何Reentrantlock锁对象的内容。认为锁定的是 Reentrantlock 内置的对象也没关系,因为锁定的内容完全可以不用关心。

② Reentrantlock为什么能够实现可中断的线程响应?

根据上面synchronized关键字实现的中断中,已经知道synchronized不能实现响应式中断的原因是:不能知道线程能否获取到锁,即JDK没有提供代码给程序员使用说:"我的代码获取到锁了吗"这个条件。Reentrantlock 提供了一个获取这个条件的方法:tryLock(),该方法可以测试,代码到底能否获取到锁。个人猜测底层也是通过这个方法去实现响应式中断线程的。

1.1.3 ReentrantLock实现多条件变量的控制  

使用synchronized关键字控制线程间的通讯,基本通过wait和notify两个方法把线程给阻塞以及唤醒,来协调两个线程之间的通讯。当使用多个条件的时候,发现使用synchronized很难去实现。例如:生产者-消费者模式中,假如仓库里面的库存已经没法容纳更多的产品,这时候应该调用notify方法把消费者线程唤醒,生产者线程进入休眠。但synchronized的方法没办法通过notify方法唤醒消费者。在调用notify的时候,唤醒的是所有等待锁的线程对象;这时候等待锁的可能是消费者也有可能是生产者,如果唤醒的是生产者,那么生产者又进入了休眠。这样将会导致程序的执行效率比较低。如果有仓库满了,有方法唤醒消费者线程就好了。这时候,ReentrantLock的按对象唤醒就派上用场了;这个也是synchronized处理不了的。

 基于这个内容,先看下JDK API 所提供按条件唤醒、休眠的例子。

 package com.scl.thread.lock.condition;

 import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; 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();
}
}
}

API condition 实现缓冲区例子

API 中就是使用了condition实现按条件唤醒线程功能。使用一个condition同样能够实现功能,但效率可能不高。

使用ReetrantLock及condition更改进出库内的生产者-消费者模型。

 package com.scl.thread.lock.condition;

 import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; public class TestCarPark
{
public static void main(String[] args)
{
// 假设车库内有3个车辆使出者,5个使用的.模拟工作五分钟的情况
ExecutorService driverInWorkers = Executors.newFixedThreadPool(5);
ExecutorService driverOutWorkers = Executors.newFixedThreadPool(3);
long startTime = System.currentTimeMillis();
CarPark carPark = new CarPark(10); while (true)
{ long endTime = System.currentTimeMillis();
if (endTime - startTime > 2000)
{
// 程序运行20秒后,不再加任务
driverOutWorkers.shutdown();
driverInWorkers.shutdown();
break;
}
else
{
try
{
Thread.sleep(50);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
// 不断地加减任务
driverOutWorkers.submit(new Secute(carPark));
driverInWorkers.submit(new CarOwner(carPark));
}
}
// 如果线程池任务都已经完成, 则退出线程池
if (driverInWorkers.isTerminated() && driverOutWorkers.isTerminated())
{
driverOutWorkers.shutdownNow();
driverInWorkers.shutdownNow();
}
}
}

客户端测试代码

 package com.scl.thread.lock.condition;

 //模拟车子类,设置成空
public class Car
{
public Car()
{
}
}

模拟汽车类

 package com.scl.thread.lock.condition;

 public class CarOwner implements Runnable
{
private CarPark carPark; public CarOwner(CarPark carPark)
{
this.carPark = carPark;
} @Override
public void run()
{
carPark.driverIn();
}
}

持车人 CarOwner

 package com.scl.thread.lock.condition;

 public class Secute implements Runnable
{
private CarPark carPark; public Secute(CarPark carPark)
{
this.carPark = carPark;
} @Override
public void run()
{
carPark.driverOut();
}
}

车库出车人 Secute

 package com.scl.thread.lock.condition;

 import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock; /**
*
* @author scl
*
* @fileName CarPark.java
*
* @time 2016下午3:42:35
*
* declaration: 模拟车库每次进出一辆车子
*/
public class CarPark
{
protected int MaxCarNum;
private volatile List<Car> carList = new ArrayList<Car>();
private ReentrantLock reLock = new ReentrantLock();
private Condition driverInCon = reLock.newCondition();
private Condition driverOutCon = reLock.newCondition(); public CarPark(int maxNum)
{
this.MaxCarNum = maxNum;
} public void driverIn()
{
reLock.lock();
try
{
// while (true)
// {
//
// if (carList.size() + 1 > MaxCarNum)
// {
// System.out.println(Thread.currentThread().getName() +
// " 当前车库车辆数目:" + carList.size() + "车库满了,不能再入库了");
//
// driverInCon.await();
// }
// else
// {
// carList.add(new Car());
// Thread.sleep(300);
// System.out.println(Thread.currentThread().getName() +
// " 已入库1辆汽车,当前车库车辆数目: " + carList.size());
// // 从这句代码可以看出signal并不释放锁
// driverOutCon.signal();
// }
// }
while (carList.size() + 1 > MaxCarNum)
{
System.out.println(Thread.currentThread().getName() + " 当前车库车辆数目:" + carList.size() + "车库满了,不能再入库了");
driverInCon.await();
} carList.add(new Car());
Thread.sleep(30);
System.out.println(Thread.currentThread().getName() + " 已入库1辆汽车,当前车库车辆数目: " + carList.size());
// signal不会 释放锁,也不会唤醒某个线程,只是在condition队列里面把某条线程出列
driverOutCon.signal(); }
catch (Exception e)
{
e.printStackTrace();
}
finally
{
reLock.unlock();
} } public void driverOut()
{
reLock.lock();
try
{
// while (true)
// {
//
// if (carList.size() - 1 < 0)
// {
// System.out.println(Thread.currentThread().getName() +
// " 车库没有车了,当前车库车辆数目: " + carList.size());
// driverOutCon.await();
// }
// else
// {
// // 使出一辆
// carList.remove(0);
// Thread.sleep(300);
// System.out.println(Thread.currentThread().getName() +
// " 已经使出一辆车,当前车库车辆数目:" + carList.size());
// // signal不会 释放锁
// driverInCon.signal();
// }
// } while (carList.size() - 1 < 0)
{
System.out.println(Thread.currentThread().getName() + " 车库没有车了,当前车库车辆数目: " + carList.size());
driverOutCon.await();
} // 使出一辆
carList.remove(0);
Thread.sleep(30);
System.out.println(Thread.currentThread().getName() + " 已经使出一辆车,当前车库车辆数目:" + carList.size());
// signal不会 释放锁,也不会唤醒某个线程,只是在condition队列里面把某条线程出列
driverInCon.signal(); }
catch (Exception e)
{
e.printStackTrace();
}
finally
{
reLock.unlock();
}
}
}

停车场 CarPark

在开始写停车场类的时候,代码无意中写错,发现一个问题:signal方法根本不能释放对象锁且不能唤醒任何线程,查看JDK内的notify方法进行了一下比较。API内注明如下:

直到当前线程放弃此对象上的锁定,才能继续执行被唤醒的线程 ;后来搜索了一下他人的博客,发现Condition同样维护着一个队列,当调用await的时候,线程被扔进Condition的队列内,直到调用signal且函数释放了对象锁,才会对线程进行唤醒。相关博客地址:http://www.liuinsect.com/2014/01/27/how_to_understand_condition

同样地,可以对停车场内写错的代码进行使用进行验证该观点。

2. ReentrantReadWriteLock (读写锁)

  在使用数据库事务的时候,数据库在处理事务时有两个很重要的锁。一个是读锁,一个是写锁。读锁共享,能跟其他读锁并存;写锁排它,写锁与读锁互斥、写锁与写锁互斥。JDK同样地在控制多线程的时候有这读、写这两把锁。  

首先要指出的是ReentrantLock与ReentrantLock没有任何联系。唯一有联系的就是两种不同的锁都是基于AQS进行实现的。

Hibernate 里面有个叫延迟加载的功能,跟读写锁很相似。读的时候,在内存里面查找。如果内存没有,则查询数据库。

 package com.scl.thread.lock.condition;

 import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock; public class CacheDemo
{
// 模拟内存中缓存的数据
private Map<String, Object> map = new HashMap<String, Object>();
private ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock(); // 获取对象
private Object getObject(String id)
{
Object data = map.get(id);
// 写锁是共享的,可以多条线程进入访问
rwlock.readLock().lock();
try
{
// 如果内存里面该值为空
if (data == null)
{
// 释放写锁
rwlock.readLock().unlock();
// 添加写锁
rwlock.writeLock().lock();
// 如果某一线程在特定时间点读到数据则不再访问数据库。防止线程重读
if (data == null)
{
try
{
data = readDataFromDB(id);
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
// 释放写锁,重新上读锁
rwlock.writeLock().unlock();
rwlock.readLock().lock();
}
}
}
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
rwlock.readLock().unlock();
}
return data;
} private Object readDataFromDB(String id)
{
// 模拟从数据库读取数据
return new Object();
} }

模拟缓存代码

JDK Api中也有模拟缓存的例子,还用了锁降级。不是很理解锁降级的具体作用。后面具体查找下原因再补充。

 package com.scl.thread.lock.condition;

 import java.util.concurrent.locks.ReentrantReadWriteLock;

 class CachedData
{
Object data;
volatile boolean cacheValid;
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); void processCachedData()
{
rwl.readLock().lock();
if (!cacheValid)
{
// Must release read lock before acquiring write lock
rwl.readLock().unlock();
rwl.writeLock().lock();
// Recheck state because another thread might have acquired
// write lock and changed state before we did.
if (!cacheValid)
{
data = getDataFromDB();
cacheValid = true;
}
// Downgrade by acquiring read lock before releasing write lock
rwl.readLock().lock();
rwl.writeLock().unlock(); // Unlock write, still hold read
}
// use data to do something
use(data);
rwl.readLock().unlock();
} // 模拟调用数据库
private Object getDataFromDB()
{
return new Object();
} // 模拟使用数据
private void use(Object data)
{
System.out.println(data.toString());
}
}

JDK操作缓存示例代码

以上为本次对JDK5上面锁的总结,如有问题,烦请指出纠正。

Java多线程(五) Lock接口,ReentranctLock,ReentrantReadWriteLock的更多相关文章

  1. Java多线程的~~~Lock接口和ReentrantLock使用

    在多线程开发.除了synchronized这个keyword外,我们还通过Lock接口来实现这样的效果.由Lock接口来实现 这样的多线程加锁效果的优点是非常的灵活,我们不在须要对整个函数加锁,并且能 ...

  2. java多线程(五)-访问共享资源以及加锁机制(synchronized,lock,voliate)

    对于单线程的顺序编程而言,每次只做一件事情,其享有的资源不会产生什么冲突,但是对于多线程编程,这就是一个重要问题了,比如打印机的打印工作,如果两个线程都同时进行打印工作,那这就会产生混乱了.再比如说, ...

  3. java多线程(五)之总结(转)

    引 如果对什么是线程.什么是进程仍存有疑惑,请先Google之,因为这两个概念不在本文的范围之内. 用多线程只有一个目的,那就是更好的利用cpu的资源,因为所有的多线程代码都可以用单线程来实现.说这个 ...

  4. Java多线程基础——Lock类

    之前已经说道,JVM提供了synchronized关键字来实现对变量的同步访问以及用wait和notify来实现线程间通信.在jdk1.5以后,JAVA提供了Lock类来实现和synchronized ...

  5. Java中的Lock接口

    Synchronized & Lock synchronized 是Java语言中的关键字,由monitorenter,monitorexit两个指令实现.JVM会将monitorenter指 ...

  6. java 多线程:Callable接口;FutureTask类实现对象【Thread、Runnable、Callable三种方式实现多线程的区别】

    Callable接口介绍: Java5开始,Java提供了Callable接口,像是Runnable接口的增强版,Callable接口提供了一个 call()方法可以作为线执行体. call()方法比 ...

  7. Java多线程(五) —— 线程并发库之锁机制

    参考文献: http://www.blogjava.net/xylz/archive/2010/07/08/325587.html 一.Lock与ReentrantLock 前面的章节主要谈谈原子操作 ...

  8. Java 多线程(五)之 synchronized 的使用

    目录 1 线程安全 2 互斥锁 3 内置锁 synchronized 3.1 普通同步方法,锁是当前实例对象(this) 3.1.1 验证普通方法中的锁的对象是同一个. 3.1.2 验证不同的对象普通 ...

  9. java多线程--实现Runnable接口方式

    因为java类只能继承一个类可以实现多个接口的特性,所以一般情况下不推荐使用继承Thread类实现多线程,下面是实现Runnable接口方式的简单多线程代码 package text; /** * 多 ...

随机推荐

  1. lisener在web.xml中设置

    /* * servlet监听器开发步骤: * 1.写一个类实现XXXListener接口(6个=3个容器+3个对容器中属性进行操作) * 2.在web.xml中配置<listener> - ...

  2. Linux命令行技巧

    Linux命令行技巧 命令 描述 • apropos whatis 显示和word相关的命令. 参见线程安全 • man -t man | ps2pdf - > man.pdf 生成一个PDF格 ...

  3. Java SE ---算术运算符

    算术运算符:(加)+,(减)-,(乘)*,(除)/,(求余)%,自增自减 一,算数运算符:当有若干个变量参与运算时,结果类型取决于这些变量中表示范围最大的那个变量类型.如果参加运算的变量中有整型int ...

  4. Spring与Hibernate、Mybatis整合

    在Web项目中一般会把各个web框架结合在一起使用,比如spring+hibernate,spring+ibatis等,如此以来将其他的框架整合到spring中来,便有些少许的不便,当然spring已 ...

  5. iOS之可拖拽重排的CollectionView

    修复了拖拽滚动时抖动的一个bug,新增编辑模式,进入编辑模式后不用长按触发手势,且在开启抖动的情况下会自动进入抖动模式,如图: test.gif 图1:垂直滚动 drag1.gif 图2:水平滚动 d ...

  6. 用python理解web并发模型

    最简单的并发 import socket response = 'HTTP/1.1 200 OK\r\nConnection:Close\r\nContent-Length:11\r\n\r\nHel ...

  7. Obout - ASP.NET HTML Editor

    ASP.NET MVC HTML Editor http://www.obout.com/mvc-editor/index.aspx http://www.obout.com/index.aspx H ...

  8. img与父元素的间隙解决

    近来在做H5页面时,突然发现一个问题,使用一个div包裹一个img,在手机预览时,发现图片与div之间有间隙. 当时第一反应就是,是不是间距没有设置为0,于是预览了下代码: .active img { ...

  9. this指针在不同情况下的指代

     说不同情况了吧,首先要分有几种情况使用this,然后再说分别指代什么 1)如果是一般标签下函数调用,this指代全局对象,也就是window对象或者document对象 2)如果在嵌套函数中被嵌套的 ...

  10. HDOJ2027统计元音

    统计元音 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submis ...