前言
在Java 多线程(二) Synchronized与Volatile关键字内我们介绍了synchronized
关键字,以及与锁之间的关系.在本章中,我们将继续介绍这部分的内容
同时,我们可以解决在Java 多线程(三) 线程通信中提及的,无法唤醒某个特定线程的问题.(Condition类进行解决) 此外我们还将介绍读锁与写锁,以及它们的优势.
本章的主要内容如下所示:
- ReentrantLock类的基本使用
- Condition类的基本使用
- 公平锁与非公平锁
- ReentrantReadWriteLock类的基本使用
正文
ReentrantLock的基本使用
在介绍synchronized关键字的时候,我们会说.在运行synchronized
方法块内的方法可以获取到对象或类的锁, 在退出时释放锁. 其次在调用wait()
与join()
方法的时候都会释放锁, sleep()
方法不会释放锁. 基于这些规则与原理, JDK 1.5
之后,人们将锁的概念抽象出来,创建了ReentrantLock类
.
ReentrantLock类
的基本使用也是非常简单的. 与synchronized关键字
一样,我们在运行时通过lock()
方法进行锁定,通过unlock()
方法进行释放. 基本实例如下所示:
import java.util.concurrent.locks.ReentrantLock;
class ReentrantLockService{
private ReentrantLock lock = new ReentrantLock();
public int i=0;
public void increase(){
lock.lock();
i++;
lock.unlock();
}
}
class ReentrantLockThread extends Thread{
public ReentrantLockService service;
public ReentrantLockThread(ReentrantLockService service){
this.service = service;
}
public void run(){
for(int i=0;i<1000;i++){
service.increase();
}
}
}
public class ReentrantLockThreadDemo {
public static void main(String []args){
ReentrantLockService service = new ReentrantLockService();
Thread threadA = new ReentrantLockThread(service);
Thread threadB = new ReentrantLockThread(service);
threadA.start();
threadB.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("i="+service.i);
}
}
// i=2000
当然,与synchronized
关键字一样.对于lock()
方法后的方法,我们采用同步执行.对于非lock()
后的方法,我们仍然可以采用异步的措施进行执行.
公平锁与非公平锁
公平和非公平其实是ReentrantLock类
的一个属性.公平指的是程序获取锁的顺序是按照线程加锁的顺序来分配的. 而非公平锁指的是抢占式的获取锁, 即随机获取.
锁的公平与非公平的属性声明也非常的简单. 实例如下:
ReentrantLock fairLock = new ReentrantLock(true); //公平锁
ReentrantLock unfairLock = new ReentrantLock(false); //非公平锁 默认声明的是非公平锁
对于公平锁与非公平锁的问题, 我们也举一个例子来实践这个问题.
import java.util.concurrent.locks.ReentrantLock;
/**
* 公平锁 与 非公平锁.
*
* */
class FairLockThread extends Thread{
private ReentrantLock lock;
public FairLockThread(ReentrantLock lock){
this.lock = lock;
}
public void run(){
try{
lock.lock();
System.out.println("Thread: "+Thread.currentThread().getName()+System.currentTimeMillis());
}finally{
lock.unlock();
}
}
}
public class FairReentrantLock {
public static void main(String[] args) {
ReentrantLock fairLock = new ReentrantLock(true);
Thread threadA = new FairLockThread(fairLock);
Thread threadB = new FairLockThread(fairLock);
Thread threadC = new FairLockThread(fairLock);
Thread threadD = new FairLockThread(fairLock);
Thread threadE = new FairLockThread(fairLock);
threadA.start();
threadB.start();
threadC.start();
threadD.start();
threadE.start();
}
}
//Thread: Thread-01553001414577
//Thread: Thread-11553001414578
//Thread: Thread-21553001414579
//Thread: Thread-31553001414579
//Thread: Thread-41553001414579
将上述的例子中的锁创建是变为:
ReentrantLock fairLock = new ReentrantLock(false);
// Thread: Thread-01553001481846
// Thread: Thread-31553001481847
// Thread: Thread-41553001481847
// Thread: Thread-21553001481847
// Thread: Thread-11553001481847
可以看到上述的顺序变成了乱序的.(因为此处变成了非共享锁.)
Condition类的基本使用
在前文中,我们提过Thread的wait(),notify()无法指定唤醒某个线程
. 面对这个问题,JDK1.5之后提出了Condition类
的概念解决这个问题.
Condition类是基于某个锁的Condition类.一个锁可以具有多个Condition类
.我们同样使用Condition类
来实现第三章中出现的wait/notify机制
.
首先,为了显示的简单,我们举一个简单的例子加以说明.
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
class ConditionWaitThread extends Thread{
ReentrantLock lock;
Condition condition;
public ConditionWaitThread(ReentrantLock lock,Condition condition){
this.lock = lock;
this.condition = condition;
}
// 等待线程
public void run(){
try{
lock.lock();
System.out.println("Thread: "+Thread.currentThread().getName()+System.currentTimeMillis()+" Await begin.");
condition.await();
System.out.println("Thread: "+Thread.currentThread().getName()+System.currentTimeMillis()+" Await end.");
}catch(InterruptedException e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
}
class ConditionNotifyThread extends Thread{
ReentrantLock lock;
Condition condition;
public ConditionNotifyThread(ReentrantLock lock,Condition condition){
this.lock = lock;
this.condition = condition;
}
// 等待线程
public void run(){
try{
lock.lock();
System.out.println("Thread: "+Thread.currentThread().getName()+System.currentTimeMillis()+" Notify begin.");
condition.signal();
System.out.println("Thread: "+Thread.currentThread().getName()+System.currentTimeMillis()+" Notify end.");
}finally{
lock.unlock();
}
}
}
public class ConditionDemo {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
Thread waitThread = new ConditionWaitThread(lock,condition);
Thread notiyThread = new ConditionNotifyThread(lock,condition);
waitThread.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
notiyThread.start();
}
}
//Thread: Thread-01553002264752 Await begin.
//Thread: Thread-11553002266753 Notify begin.
//Thread: Thread-11553002266754 Notify end.
//Thread: Thread-01553002266754 Await end.
输出与我们预想的几乎一致.在例子中我们使用condition.await() / condition.singnal()
替换了原始的wait() / notify()
方法. 与wait() / notify()
一致的是,condition
的await() / singnal()
方法也具有如下的几个特性:
- 必须先获取对象的锁才能调用
await()
方法,否则会抛出异常. await()
方法调用后,会释放对象的锁;singnal()
方法调用后不会立即释放对象的锁,而是等当前的进程运行完毕后才会.await()
方法也有await(long)
的超时等待机制,且等待时被interrupt()
中断也会抛出异常;singnal()
唤醒单个;signalAll()
唤醒多个.
在实际中,有时我们需要维护一个Lock锁的多个Condition对象
.用于特定唤醒某个线程.我们将上文的实例改写下即可.其实例如下:
public class ConditionDemo {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Condition conditionA = lock.newCondition();
Condition conditionB = lock.newCondition();
Thread waitThreadA = new ConditionWaitThread(lock,conditionA);
Thread waitThreadB = new ConditionWaitThread(lock,conditionB);
Thread notiyThread = new ConditionNotifyThread(lock,conditionA);
waitThreadA.start();
waitThreadB.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
notiyThread.start();
}
}
//Thread: Thread-01553002848245 Await begin.
//Thread: Thread-11553002848246 Await begin.
//Thread: Thread-21553002850248 Notify begin.
//Thread: Thread-21553002850248 Notify end.
//Thread: Thread-01553002850248 Await end.
当然,使用ReentrantLock
与Condition
类也能够完成我们在第三章中的 生产者 / 访问者
的队列.这里,我们使用阻塞队列CopyOnWriteArrayList
线程安全的阻塞队列作为传输消息的媒介.
import java.util.List;
import java.util.concurrent.BlockingDeque;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
class ProducerLockConditionListThread extends Thread{
ReentrantLock lock;
Condition condition;
List list;
public ProducerLockConditionListThread(ReentrantLock lock, Condition condition, List list){
this.lock = lock;
this.condition = condition;
this.list = list;
}
public void produce(){
try{
lock.lock();
while(ProducerConsumerLockConditionListThread.index != list.size()){
condition.await();
}
String msg = "MSG"+System.currentTimeMillis();
list.add(msg);
System.out.println("Produce"+"Thread: "+Thread.currentThread().getName()+System.currentTimeMillis()+" "+msg);
condition.signal();
}catch(InterruptedException e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
public void run(){
while(true){
produce();
}
}
}
class ConsumerLockConditionListThread extends Thread{
ReentrantLock lock;
Condition condition;
List list;
public ConsumerLockConditionListThread(ReentrantLock lock, Condition condition, List list){
this.lock = lock;
this.condition = condition;
this.list = list;
}
public void consume(){
try{
lock.lock();
while(ProducerConsumerLockConditionListThread.index == list.size()){
condition.await();
}
String msg = (String)list.get(ProducerConsumerLockConditionListThread.index++);
System.out.println("Consume"+"Thread: "+Thread.currentThread().getName()+System.currentTimeMillis()+" "+msg);
condition.signal();
}catch(InterruptedException e){
e.printStackTrace();
}finally{
lock.unlock();
}
}
public void run(){
while(true){
consume();
}
}
}
public class ProducerConsumerLockConditionListThread {
public volatile static int index;
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
List list = new CopyOnWriteArrayList<>();
Thread producerThread = new ProducerLockConditionListThread(lock,condition,list);
Thread consumerThread = new ConsumerLockConditionListThread(lock,condition,list);
producerThread.start();
consumerThread.start();
}
}
// ProduceThread: Thread-01553003849154 MSG1553003849154
// ConsumeThread: Thread-11553003849155 MSG1553003849154
// ProduceThread: Thread-01553003849155 MSG1553003849155
// ConsumeThread: Thread-11553003849155 MSG1553003849155
在实例中,有时我们不需要这样的交替打印的情况.这边给出一个实例给大家自己执行.
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* * Producer Consumer Problem solution using BlockingQueue in Java. *
* BlockingQueue not only provide a data structure to store data * but also
* gives you flow control, require for inter thread communication. * * @author
* Javin Paul
*/
public class ProducerConsumerSolution {
public static void main(String[] args) {
BlockingQueue<Integer> sharedQ = new LinkedBlockingQueue<Integer>();
Producer p = new Producer(sharedQ);
Consumer c = new Consumer(sharedQ);
p.start();
c.start();
}
}
class Producer extends Thread {
private BlockingQueue<Integer> sharedQueue;
public Producer(BlockingQueue<Integer> aQueue) {
super("PRODUCER");
this.sharedQueue = aQueue;
}
public void run() {
// no synchronization needed
for (int i = 0; i < 10; i++) {
try {
System.out.println(getName() + " produced " + i);
sharedQueue.put(i);
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Consumer extends Thread {
private BlockingQueue<Integer> sharedQueue;
public Consumer(BlockingQueue<Integer> aQueue) {
super("CONSUMER");
this.sharedQueue = aQueue;
}
public void run() {
try {
while (true) {
Integer item = sharedQueue.take();
System.out.println(getName() + "consumed " + item);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// Output PRODUCER produced 0
// CONSUMER consumed 0 PRODUCER produced 1 CONSUMER consumed
// 1 PRODUCER produced 2 CONSUMER consumed 2 PRODUCER
// produced 3 CONSUMER consumed 3 PRODUCER produced 4
// CONSUMER consumed 4 PRODUCER produced 5 CONSUMER consumed
// 5 PRODUCER produced 6 CONSUMER consumed 6 PRODUCER
// produced 7 CONSUMER consumed 7 PRODUCER produced 8
// CONSUMER consumed 8 PRODUCER produced 9 CONSUMER consumed
// 9
Producer Consumer Solution using BlockingQueue in Java Thread
ReentrantLock 其他API
int getHoldCount()
获取锁定个数,即调用lock.lock()
方法的次数.int getQueueLength()
返回正在等待获取此锁定的线程估计数.int getWaitQueueLength(Condition condition)
返回等待与此锁定相关的给定条件Condition的线程估计数.boolean hasQueuedThread(Thread thread)
查询指定的线程是否正在等待此锁定.boolean hasWaiters(Condition condition)
查询是否有线程正在等待此锁定相关的Condition条件.boolean hasWaiters()
boolean isFair()
判断是否是公平锁.boolean isHeldByCurrentThread()
查询当前线程是否保持此锁定.boolean isLocked()
查询此锁定是否由任意线程保持.lockInterrupted()
如果当前线程未被中断,则获取锁定;如果已经被中断则出现异常.boolean tryLock()
仅在调用时锁定未被另一个线程保持的情况下,才获取此锁定.boolean tryLock(long timeout, TimeUnit unit)
如果锁定在给定等待的时间内没有被另一个线程保持, 且当前线程未被中断, 则获取该锁定.awaitUninterrupteibly()
awaitUntil()
Condition有时还可以将任务进行顺序的安排.
ReentrantReadWriteLock
读写锁是为了线程的高效运转. 读写锁包括两个锁,读锁和写锁.读锁,是共享锁,两两不互斥;写锁,是排它锁,两两互斥.
我们下面将介绍下面读四种情况的例子:
- 读读共享 2. 写写互斥 3. 写读互斥 4. 读写互斥
- 读读共享
/**
* 4-2-1 ReentrantReadWriteLock读读共享.
* 使用lock.readLock()读锁可以提高程序的运行效率.
*
* */
class ReadReadLockService{
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void read(){
try{
lock.readLock().lock();
System.out.println("Thread:"+Thread.currentThread().getName()+"Read."+System.currentTimeMillis());
}finally{
lock.readLock().unlock();
}
}
}
class ReadReadLockThread extends Thread{
ReadReadLockService service;
public ReadReadLockThread(ReadReadLockService service){
this.service = service;
}
public void run(){
service.read();
}
}
public class ReadReadLock {
public static void main(String[] args) {
ReadReadLockService service = new ReadReadLockService();
Thread threadA = new ReadReadLockThread(service);
Thread threadB = new ReadReadLockThread(service);
threadA.start();
threadB.start();
}
}
//Thread:Thread-0Read.1553011468326
//Thread:Thread-1Read.1553011468326
- 写写互斥
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 4-2-2 ReentrantReadWriteLock写写互斥.
* 使用lock.readLock()读锁可以提高程序的运行效率.
*
* */
class ReadWriteLockService{
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void read(){
try{
lock.readLock().lock();
System.out.println("Thread:"+Thread.currentThread().getName()+"Read."+System.currentTimeMillis());
}finally{
lock.readLock().unlock();
}
}
public void write(){
try{
lock.writeLock().lock();
System.out.println("Thread:"+Thread.currentThread().getName()+"Write."+System.currentTimeMillis());
}finally{
lock.writeLock().unlock();
}
}
}
class WriteLockThread extends Thread{
ReadWriteLockService service;
public WriteLockThread(ReadWriteLockService service){
this.service = service;
}
public void run(){
service.write();
}
}
class ReadLockThread extends Thread{
ReadWriteLockService service;
public ReadLockThread(ReadWriteLockService service){
this.service = service;
}
public void run(){
service.read();
}
}
public class WriteWriteLock {
public static void main(String[] args) {
ReadWriteLockService service = new ReadWriteLockService();
Thread threadA = new ReadLockThread(service);
Thread threadB = new WriteLockThread(service);
threadA.start();
threadB.start();
}
}
//Thread:Thread-0Read.1553014692146
//Thread:Thread-1Write.1553014692147
Reference
[1]. Java 多线程编程核心技术
[2]. Java并发编程的艺术