Java基础--并发实用工具(4)

Java可重入锁及Condition线程间通信

java.util.concurrent.locks包对锁提供了支持,锁是一些对象,他们为使用synchronized控制对共享资源的访问提供了替代技术。大体而言,锁的工作原理如下:在访问共享资源之前,申请用于保护资源的锁;当资源访问完成,释放锁。当某个线程正在使用锁时,如果另一个线程尝试申请锁,那么后者将被挂起,直到锁释放为止。通过这种方式,可以防止多线程对共享资源的冲突访问。
对比,使用Java内置的同步特性(使用同步块或者同步方法),使用锁的的优势在什么地方?我们知道,只要是有线程进入到对象监视器的内部(即同步块或者同步方法中),其他的线程就只能等待。但是,我们还想到,如果这几个线程中有只读线程也有写线程,那么,对于只读线程可以让他们一下子都进去监视器内部访问,而对于写线程则是互斥访问,这是我们想的,但是同步块和同步方法是不分读写的,只要是有线程进去就不让其他线程进来了。而锁可以做到这一点,因为锁除了有和同步特性对应的ReentrantLock,还有ReentrantReadWriteLock。除此之外,更重要的还是Synchronized很容易膨胀为重量级锁,相对于Lock而言,性能较低。『锁的详细说明见其他博文』
可重入锁:同步块同步方法、ReentrantLock/ReentrantReadWriteLock都是可重入锁。Java为每个锁关联了一个请求计数器和占有它的线程,当请求计数器为0时,表示这个锁没有被请求持有,如果不为零,表示有线程请求持有,每次有一个线程请求,计数器加一,线程退出监视器后,计数器减一。那可重入锁是什么呢?线程A进入到了X对象的监视器内部,那线程A已经拿到了X对象的锁,如果在X对象内部调用了其他对象Y监视器内部的操作,而且Y对象监视器没有被其他对象持有,那线程A就要进入到Y对象监视器内部,但是,如果锁不是可重入的,JVM会判断:线程A已经拿到了X对象监视器的锁,不能再拿其他对象监视器的锁了,所以就造成了『死锁』(不同于平常的死锁,这里的死锁是因为线程A不能正常终结而不能释放锁);而锁的可重入性就是用来解决这个问题的:Java中锁的分配是按照线程分配的(即使该线程进入到了不同对象的监视器内部,表面上拿到了许多对象的锁),每个线程分配一个锁,这个锁是可重入的(一个锁可以进入到不同对象的监视器内部);而不是基于调用分配锁的,基于调用分配锁,是每次进入到对象监视器的内部都分配一个锁给这个线程。简而言之:Java为每个线程分配一个锁,而不是为每次调用分配一个锁。可重入锁的参考
对应于同步块和同步方法的ReentrantLock使用大致如下:多个需要同步的线程使用同一个锁,在访问资源前,先获取锁,访问资源之后,释放锁。实例代码如下:
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockTest {

	public static void main(String[] args) {
		ReentrantLock reentrantLock = new ReentrantLock();
		new Thread(()->{
			int i = 0;
			while(i<3){
				//获取锁,获取不到就等待
				reentrantLock.lock();
				//获取之后进行一系列操作
				System.out.println("Thread-0 get the lock...");
				System.out.println("I'm Thread-0,I hold the lock,but I'll sleep for a while,give others the chance to try to get the lock..");
				try {
					Thread.sleep(1000);
				} catch (Exception e) {
					e.printStackTrace();
				}
				System.out.println("I'm Thread-0,I'm awake now,I'll release the lock..");
				i++;
				reentrantLock.unlock();
			}
		}).start();
		new Thread(()->{
			int i = 0;
			while(i<3){
				//获取锁,获取不到就等待
				reentrantLock.lock();
				//获取之后进行一系列操作
				System.out.println("Thread-1 get the lock...");
				System.out.println("I'm Thread-1,I hold the lock,but I'll sleep for a while,give others the chance to try to get the lock..");
				try {
					Thread.sleep(1000);
				} catch (Exception e) {
					e.printStackTrace();
				}
				System.out.println("I'm Thread-1,I'm awake now,I'll release the lock..");
				i++;
				reentrantLock.unlock();
			}
		}).start();
	}
//	运行结果:
//	Thread-0 get the lock...
//	I'm Thread-0,I hold the lock,but I'll sleep for a while,give others the chance to try to get the lock..
//	I'm Thread-0,I'm awake now,I'll release the lock..
//	Thread-0 get the lock...
//	I'm Thread-0,I hold the lock,but I'll sleep for a while,give others the chance to try to get the lock..
//	I'm Thread-0,I'm awake now,I'll release the lock..
//	Thread-0 get the lock...
//	I'm Thread-0,I hold the lock,but I'll sleep for a while,give others the chance to try to get the lock..
//	I'm Thread-0,I'm awake now,I'll release the lock..
//	Thread-1 get the lock...
//	I'm Thread-1,I hold the lock,but I'll sleep for a while,give others the chance to try to get the lock..
//	I'm Thread-1,I'm awake now,I'll release the lock..
//	Thread-1 get the lock...
//	I'm Thread-1,I hold the lock,but I'll sleep for a while,give others the chance to try to get the lock..
//	I'm Thread-1,I'm awake now,I'll release the lock..
//	Thread-1 get the lock...
//	I'm Thread-1,I hold the lock,but I'll sleep for a while,give others the chance to try to get the lock..
//	I'm Thread-1,I'm awake now,I'll release the lock..
	/*
	 * 很显然这是由于锁的作用哦
	 */
}
ReentrantReadWriteLock支持了读锁和写锁的分离,读锁之间可以同时进行,写锁可以下降为读锁,读锁不能上升为写锁,写锁和读写锁都互斥。使用的实例代码如下:
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReentrantReadWriteLockTest {

	public static void main(String[] args) {
		/*
		 * 有两个线程对公共资源进行写,使用写锁,有两个线程对公共资源进行读,使用读锁
		 */
		ReentrantReadWriteLock rrrl = new ReentrantReadWriteLock();
		new Thread(()->{
			//第一个写线程
			int i = 0;
			while(i<2){
				rrrl.writeLock().lock();
				System.out.println("the first write thread...");
				try {
					//给其他线程竞争锁的机会
					Thread.sleep(10);
				} catch (Exception e) {
					e.printStackTrace();
				}
				System.out.println("the first write thread done...");
				i++;
				rrrl.writeLock().unlock();
			}
		}).start();
		new Thread(()->{
			//第二个写线程
			int i = 0;
			while(i<2){
				rrrl.writeLock().lock();
				System.out.println("the second write thread...");
				try {
					//给其他线程竞争锁的机会
					Thread.sleep(10);
				} catch (Exception e) {
					e.printStackTrace();
				}
				System.out.println("the second write thread done...");
				i++;
				rrrl.writeLock().unlock();
			}
		}).start();
		new Thread(()->{
			//第一个读线程
			int i = 0;
			while(i<2){
				rrrl.readLock().lock();
				System.out.println("the first read thread...");
				try {
					//给其他线程竞争锁的机会
					Thread.sleep(10);
				} catch (Exception e) {
					e.printStackTrace();
				}
				System.out.println("the first read thread done...");
				i++;
				rrrl.readLock().unlock();
			}
		}).start();
		new Thread(()->{
			//第二个读线程
			int i = 0;
			while(i<2){
				rrrl.readLock().lock();
				System.out.println("the second read thread...");
				try {
					//给其他线程竞争锁的机会
					Thread.sleep(10);
				} catch (Exception e) {
					e.printStackTrace();
				}
				System.out.println("the second read thread done...");
				i++;
				rrrl.readLock().unlock();
			}
		}).start();
	}
}
//运行结果:
//the first write thread...
//the first write thread done...
//the first write thread...
//the first write thread done...
//the second write thread...
//the second write thread done...
//the second write thread...
//the second write thread done...
//the first read thread...
//the second read thread...
//the second read thread done...
//the second read thread...
//the first read thread done...
//the first read thread...
//the second read thread done...
//the first read thread done...
我们知道,线程间通信是基于线程同步的,不同的同步方式可能对应不同的线程间通信方式,的确,之前使用Java内置的同步特性进行线程间通信使用的是Object类库的wait和notify方法,那使用锁之后的线程间通信是怎么进行的呢?Lock接口有一个newCondition()方法,这个方法返回一个Condition对象,而Condition是用来进行线程间通信的类。对于Condition来说,其await()/signal()/signalAll()分别对应Object类库中的wait()/notify()/notifyAll()三个方法,示例代码如下:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class OneCondition {
	public static void main(String[] args) {
		ReentrantLock rl = new ReentrantLock();
		Condition condition = rl.newCondition();
		new Thread(()->{
			rl.lock();
			//进来之后就进行等待
			try {
				condition.await();
				System.out.println("I'm thread-0, I'm awake now..");
			} catch (Exception e) {
				e.printStackTrace();
			}
			rl.unlock();
		}).start();
		new Thread(()->{
			rl.lock();
			//进来之后,先睡一会儿,给线程上一个线程运行的机会,即使给了他机会,因为进去之后就等待了,所以也不会向下执行
			try {
				Thread.sleep(1000);
			} catch (Exception e) {
				e.printStackTrace();
			}
			System.out.println("I'm thread-1, I will be in the firstline in the output of the console absolutely..");
			condition.signalAll();
			rl.unlock();
		}).start();
//		运行结果:
//		I'm thread-1, I will be in the firstline in the output of the console absolutely..
//		I'm thread-0, I'm awake now..
	}
}
但是,Condition类要比Object类库的三个通信方法更强大,原因就是他可以不同的线程创建多个Condition,这样和Object类库的三个方法相比,就相当于一下子有了多组这样的三个方法。实例代码如下:( 实例代码本来是想实现生产者-消费者案例,如果无限生产和无限消费,是没有问题的,但是如果指定了要生产和消费的数量,有一个bug,测试了一个下午,也没找到在哪儿:最后一次只有生产输出而没有消费输出,还求大神们看出是哪的问题?我测试的结果是最后一次生产之后,虽然在代码上唤醒了消费者,但实际上消费者并没有被唤醒。求大神不吝指教...
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class TwoConditions {

	public static void main(String[] args) {
		ReentrantLock rt = new ReentrantLock();
		Queue4 queue4 = new Queue4(rt);
		// 生产者
		new Thread(() -> {
			while (queue4.count < 5) {
				queue4.set();
			}
		}).start();
		// 消费者
		new Thread(() -> {
			while (queue4.count <= 5) {
				queue4.get();
			}
		}).start();
	}
//	运行结果:
//	Set: 1
//	Got: 1
//	Set: 2
//	Got: 2
//	Set: 3
//	Got: 3
//	Set: 4
//	Got: 4
//	Set: 5
//	#然后程序一直运行,Got:5输出不了。
}

class Queue4 {
	int count = 0;
	boolean isCon = true;
	ReentrantLock rt = null;
	Condition pro = null;
	Condition con = null;

	public Queue4(ReentrantLock rt) {
		this.rt = rt;
		pro = rt.newCondition();
		con = rt.newCondition();
	}

	public void get() {
		// 消费者
		rt.lock();
		if(isCon==false){
			System.out.println("Got: "+count);
			isCon = true;
			pro.signalAll();
			return;
		}
		
		try {
			con.await();
			/*
			 * 等待时候,count为4,等活了之后,count为5,然而count为4等待之后,就一直等待了,没有被唤醒,即使在代码上唤醒了
			 */
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		rt.unlock();
	}

	public void set() {
		// 生产者
		rt.lock();
		if(isCon==true){
			count ++;
			System.out.println("Set: "+count);
			isCon = false;
			con.signalAll();
			return;
		}
		try {
			pro.await();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		rt.unlock();
	}
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值