• java并发之BlockingQueue和Lock以及synchronized


            下面这道题是张孝祥老师整理的java面试宝典中的第28题,由于偶然的原因,看过张老师的视频,我花了8天时间将张老师的java高新技术视频给看完了,张老师讲课的诙谐幽默,让我看完这套视频觉得很轻松,很舒服。在此,感谢张老师的无私奉献,愿张老师给上帝讲java课顺利。

     题目:子线程循环10次,接着主线程循环100,接着又回到子线程循环10次,接着再回到主线程又循环100,如此循环50次,请写出程序。   

    下面总共采用三种方式进行实现,也是对学习过程的一个记录与总结。

         BlockingQueue是线程安全的,主要用于生产者-使用者的可阻塞的队列。另外支持两个附加的队列操作:获取元素时等待队列为非空,存储元素时等待队列空间变得可用

    对于支持的两个队列的操作,有以下四种实现方式。

    下面是对于上面题目的代码的实现:

    package com.undergrowth4;
    
    import java.util.concurrent.ArrayBlockingQueue;
    import java.util.concurrent.BlockingQueue;
    
    public class BlockQueueSynchr {
    
    	/**
    	 * @param args
    	 * 使用具有一个空间的队列实现同步通信
    	 * 线程只负责运行流程  共同的数据/算法封装在一个对象中  体现了高内聚 低耦合的特性
    	 */
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		final Mydata2 data=new Mydata2();
    		new Thread(new Runnable() {
    			
    			@Override
    			public void run() {
    				// TODO Auto-generated method stub
    			 for(int count=1;count<=50;count++)  //在子线程中调用data的sub执行10次 总共循环为50次
    			 {
    				 data.sub(count);
    			 }
    			}
    		}).start();
    		
    		 for(int count=1;count<=50;count++) //在主线程中调用data的main执行100次 总共循环为50次
    		 {
    			 data.main(count);
    		 }
    	}
    
    }
    
    class Mydata2{
    	//构造两个成员变量 队列容量分别为1
    	private BlockingQueue<Integer> queue1=new ArrayBlockingQueue<>(1);
    	private BlockingQueue<Integer> queue2=new ArrayBlockingQueue<>(1);
    	
    	//匿名构造器 在创建对象时会调用 先向队列1中存放一个整型数据 从而达到阻塞队列1的目的 因为要子线程先运行
    	{
    		try {
    			queue1.put(1);
    		} catch (InterruptedException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    	}
    	
    	public void main(int count)
    	{
    		try {
    			//第一次执行时 向队列1中添加数据  因为在main函数中new Mydata2()时会调用匿名构造器 所以此时无法向队列1添加数据 即在此等待可用的队列空间
    			//除去第一次之后 后面的向队列1中添加数据 只有等待sub方法中queue1.take();执行后才可执行
    			queue1.put(1); 
    		} catch (InterruptedException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    		//执行循环100次
    		for(int i=1;i<=100;i++)
    		{
    			System.out.println("第"+count+"次,main of"+i+"次");
    		}
    		try {
    			//取出队列2的数据 即让队列2有可用的空间 即让队列2执行 执行sub的循环10次
    			queue2.take();
    		} catch (InterruptedException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    	}
    	
    	public void sub(int count)
    	{
    		try {
    			//第一次执行时 向队列2中添加数据  因为队列2的空间可用 即可以向队列2添加数据
    			//除去第一次之后 后面的向队列2中添加数据 只有等待main方法中queue2.take();执行后才可执行
    			queue2.put(1);
    		} catch (InterruptedException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    		//执行循环10次
    		for(int i=1;i<=10;i++)
    		{
    			System.out.println("第"+count+"次,sub of"+i+"次");
    		}
    		try {
    			//取出队列1的数据 即让队列1有可用的空间 即让队列1执行 执行main的循环100次
    			queue1.take();
    		} catch (InterruptedException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    	}
    }
    


     

    第二种方式为Lock与Condition

      Lock是控制多个线程对共享资源进行访问的工具,提供比synchronized更为强大的功能。一个Lock对象,可以支持多个Condition对象。

      Condition实质上被绑定到一个Lock上(因为要在多个线程中访问Condition对象),在其等待条件为true之前,可一直挂起该线程

    下面使用Lock加上Condition对象机制,实现上面的题目

    package com.undergrowth4;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class MainSubThread {
    
    	/**
    	 * @param args
    	 */
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    	final MyData3 data=new MyData3();	
    		new Thread(new Runnable() {
    			
    			@Override
    			public void run() {
    				// TODO Auto-generated method stub
    				for(int count=1;count<=50;count++) //子线程调用data的sub2方法执行循环10次 总共50次
    				{
    					data.sub2(count);
    				}
    			}
    		}).start();
    		
    		
    		//主线程调用data的main方法执行循环100次 总共50次
    		for(int count=1;count<=50;count++)
    		{
    			data.main(count);
    		}
    	}
    
    }
    
    class MyData3{
    	private Lock lock=new ReentrantLock(); 
    	private Condition condition1=lock.newCondition(); //条件不满足 即挂起该线程
    	private Condition condition2=lock.newCondition();  //满足 则该线程继续执行
    	private int state=2; //首先sub先执行 接着是main
    	public void main(int count)
    	{
    		lock.lock(); //保证了同步
    		try {
    			while(state!=1)
    			{	try {
    					condition1.await(); //条件等待 条件阻塞 满足条件则唤醒
    				} catch (InterruptedException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				} 
    			}
    			for(int i=1;i<=100;i++)
    			{
    				System.out.println("all is "+count+",main run "+i);
    			}
    			state=2;
    			condition2.signal(); //唤醒sub2
    		} finally{
    			lock.unlock();
    		}
    		
    	}
    	
    	public void sub2(int count)
    	{
    		lock.lock();
    		try {
    			while(state!=2)
    			{
    				try {
    					condition2.await();
    				} catch (InterruptedException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				}
    			}
    			for(int i=1;i<=10;i++)
    			{
    				System.out.println("all is "+count+",sub2 run "+i);
    			}
    			state=1;
    			condition1.signal(); //唤醒main
    		} finally {
    			lock.unlock();
    		}
    		
    	}
    	
    	
    }
    


     

    第三种方式 synchronized与Object对象的notify、wait方法

       synchronized为互斥锁,synchronized加锁的是对象(对于多个线程而言,要是同一个对象,synchronized才有作用),而不是代码。synchronized保证了对于同一时刻最多只有一个线程执行该代码段。

        Object对象的wait、nofity方法需要与synchronized一起连用,notify唤醒在此对象监视器上的单个线程,若有多个,唤醒顺序不做保证。wait导致当前线程阻塞。

    注意:wait与notify方法只能由拥有此对象监视器的所有者的线程调用,而用synchronized互斥后,即可让该线程拥有对象的监视器。

    package com.undergrowth4;
    
    public class OrientObjectProgramThread {
    
    	/**
    	 * @param args
    	 *问题:让子线程先运行10次 然后主线程运行100次 反复50次
    	 *面向对象的编程:将共同数据/共同算法放在同一个类中
    	 *线程只适用于执行的流程 具体的实现放在一个类中 这样可以保证同步
    	 */
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		final business bus=new business();
    		new Thread(){
    			@Override
    			public void run() {
    				for(int i=1;i<=50;i++)
    					bus.sub(i);
    			};
    		}.start();
    		
    		for(int i=1;i<=50;i++)
    			bus.main(i);
    		
    	}
    
    	static class business{
    		private boolean isSubRun=true;
    		public synchronized void sub(int i) //子线程运行10次
    		{
    			while(!isSubRun){ //判断是否该自己执行
    				try {
    					this.wait();  //如果不是 则等待
    				} catch (InterruptedException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				}
    			}
    			//是自己执行  则执行
    			for(int j=1;j<=10;j++)
    			{
    				System.out.println("sub run "+j+ " 第 "+i+" 次");
    			}
    			//执行完后  修改状态 唤醒main线程
    			isSubRun=false;
    			this.notify();
    		}
    		
    		public synchronized void main(int i) //主线程运行100次
    		{
    			while(isSubRun){ //判断是否该自己执行
    				try {
    					this.wait(); //不是 则等待
    				} catch (InterruptedException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				}
    			}
    			//该自己执行
    			for(int j=1;j<=100;j++)
    			{
    				System.out.println("main run "+j+ " 第 "+i+" 次");
    			}
    			//修改状态和唤醒sub线程
    			isSubRun=true;
    			this.notify();
    		}
    	}
    }
    


     

       以上三种即是上面面试题目的解决方法。当然,还有更多的方法,知道的朋友,多多吐槽哈。

  • 相关阅读:
    PHP文件系统处理(二)
    PHP中的文件系统处理(一)
    PHP中常用正则表达式大全
    PHP中的正则表达式的使用
    SLF4J日志框架
    内部类
    计算机存储单位
    Maven 要点
    Maven 父类工程创建及引用
    Eclipse Maven Web项目创建
  • 原文地址:https://www.cnblogs.com/liangxinzhi/p/4275617.html
Copyright © 2020-2023  润新知