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

来源:互联网 发布:app商城源码 编辑:程序博客网 时间:2024/05/18 20:05

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
原创粉丝点击