Java 多线程(四) 锁与锁机制

版权声明:欢迎转载,转载请说明出处. 大数据Github项目地址https://github.com/SeanYanxml/bigdata。 https://blog.csdn.net/u010416101/article/details/88678028

前言

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()一致的是,conditionawait() / 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.

当然,使用ReentrantLockCondition类也能够完成我们在第三章中的 生产者 / 访问者的队列.这里,我们使用阻塞队列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

读写锁是为了线程的高效运转. 读写锁包括两个锁,读锁和写锁.读锁,是共享锁,两两不互斥;写锁,是排它锁,两两互斥.

我们下面将介绍下面读四种情况的例子:

  1. 读读共享 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并发编程的艺术

猜你喜欢

转载自blog.csdn.net/u010416101/article/details/88678028