java5中的线程并发库——同步集合

1. 可阻塞队列——BlockingQueue

        队列包含固定长度的队列与不固定长度的队列。

        BlockingQueue是一个接口,它支持两个附加操作的 Queue,这两个操作是:获取元素时等待队列变为非空,以及存储元素时等待空间变得可用。

1.1 API

1.1.1 插入

1. add(e):当队列已满的时候,用add就会抛异常

2. offer(e):如果队列已满,用offer添加的时候,会返回false。

3. put(e):如果队列已满,用put添加的时候,程序会阻塞,直到可以插入为止。

4. offer(e,time,unit)

1.1.2 移除

1. remove():当队列为空的时候,用remove就会抛异常

2. poll():当队列为空的时候,用poll的时候,会返回null

3. take():当队列为空的时候,用take程序会阻塞,直到队列不为空为止。

4. poll(time,unit)

1.1.3 检查

1. element():获取,但是不移除此队列的头,队列为空时将抛出一个异常

2. peek():获取但不移除此队列的头;如果此队列为空,则返回 null。

例如:

package com.bjc.thread.demo6;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockingQueueTest {
	public static void main(String[] args) {
		final BlockingQueue queue = new ArrayBlockingQueue(3);
		for(int i=0;i<2;i++){
			new Thread(){
				public void run(){
					while(true){
						try {
							Thread.sleep((long)(Math.random()*1000));
							System.out.println(Thread.currentThread().getName() + "准备放数据!");							
							queue.put(1);
							System.out.println(Thread.currentThread().getName() + "已经放了数据," + 							
										"队列目前有" + queue.size() + "个数据");
						} catch (InterruptedException e) {
							e.printStackTrace();
						}

					}
				}
				
			}.start();
		}
		
		new Thread(){
			public void run(){
				while(true){
					try {
						//将此处的睡眠时间分别改为100和1000,观察运行结果
						Thread.sleep(1000);
						System.out.println(Thread.currentThread().getName() + "准备取数据!");
						queue.take();
						System.out.println(Thread.currentThread().getName() + "已经取走数据," + 							
								"队列目前有" + queue.size() + "个数据");					
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
			
		}.start();			
	}
}

1.2 阻塞队列的简单应用

1. 用两个具有1个空间的队列来实现同步通知的功能。

Business.java

package com.bjc.thread.demo6;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

/**
 * 思路:
 * 		1. 创建两个长度为1的阻塞队列
 * 		2. 默认让队列2为满队列
 * 		3. 通过阻塞队列的API的可阻塞方法来实现线程的通信
 *
 */
public class Business {
	private BlockingQueue<Integer> q1 = new ArrayBlockingQueue<Integer>(1);
	private BlockingQueue<Integer> q2 = new ArrayBlockingQueue<Integer>(1);
	
	// 可以理解为匿名的构造函数,该代码块先于构造函数执行
	{
		try {
			// 创建对象的时候,让q2队列为满队列
			q2.put(1);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	public void sub() throws Exception{
		// 往q1队列放入元素
		q1.put(1);
		for(int i = 0 ; i < 3; i++){
			System.out.println("          sub");
		}
		// q2队列取出元素
		q2.take();
	}
	
	public void main() throws Exception{
		// 往q2队列放入元素
		q2.put(1);
		for(int i = 0 ; i < 5; i++){
			System.out.println("main");
		}
		// q1队列取出元素
		q1.take();
	}
	
}

测试方法:

package com.bjc.thread.demo6;

public class BlockingQueuCom {

	public static void main(String[] args) {
		Business b = new Business();
		new Thread(){
			@Override
			public void run() {
				try {
					for(int i = 0 ; i < 10 ; i++){
						b.sub();
					}
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}.start();
		
		new Thread(){
			@Override
			public void run() {
				try {
					for(int i = 0 ; i < 10 ; i++){
						b.main();
					}
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}.start();
	}
}

运行结果:

2. 现有的程序代码模拟产生了16个日志对象,并且需要运行16秒才能打印完这些日志,请在程序中增加4个线程去调parseLog()方法来分头打印这16个日志对象,程序只需要运行4秒即可打印完这些日志对象

package com.bjc.test;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

/**
 * @author Administrator
 * 现有的程序代码模拟产生了16个日志对象,并且需要运行16秒才能打印完这些日志,
 * 请在程序中增加4个线程去调用parseLog()方法来分头打印这16个日志对象,
 * 程序只需要运行4秒即可打印完这些日志对象
 * 
 */
public class Demo1 {

	public static void main(String[] args) throws Exception{
		BlockingQueue<String> queue = new ArrayBlockingQueue<String>(4);
		for(int i = 0 ; i < 4 ; i++){
			new Thread(){
				@Override
				public void run() {
					while(true){
						try {
							String log = queue.take();
							parseLog(log);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
				};
			}.start();
		}
        
		System.out.println("begin:"+(System.currentTimeMillis()/1000));
		/*模拟处理16行日志,下面的代码产生了16个日志对象,当前代码需要运行16秒才能打印完这些日志。
		修改程序代码,开四个线程让这16个对象在4秒钟打完。
		*/
		for(int i=0;i<16;i++){  //这行代码不能改动
			final String log = ""+(i+1);//这行代码不能改动
			{
				queue.put(log);
			}
		}
	}
	
	//parseLog方法内部的代码不能改动
	public static void parseLog(String log){
		System.out.println(log+":"+(System.currentTimeMillis()/1000));
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}		
	}

}

3. 现成程序中的Test类中的代码在不断地产生数据,然后交给TestDo.doSome()方法去处理,就好像生产者在不断地产生数据,消费者在不断消费数据。请将程序改造成有10个线程来消费生成者产生的数据,这些消费者都调用TestDo.doSome()方法去进行处理,故每个消费者都需要一秒才能处理完,程序应保证这些消费者线程依次有序地消费数据,只有上一个消费者消费完后,下一个消费者才能消费数据,下一个消费者是谁都可以,但要保证这些消费者线程拿到的数据是有顺序的

package com.bjc.test;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.SynchronousQueue;

/**
 * @author Administrator
 * 现成程序中的Test类中的代码在不断地产生数据,然后交给TestDo.doSome()方法去处理,
 * 就好像生产者在不断地产生数据,消费者在不断消费数据。请将程序改造成有10个线程来消费生成者产生的数据,
 * 这些消费者都调用TestDo.doSome()方法去进行处理,故每个消费者都需要一秒才能处理完,
 * 程序应保证这些消费者线程依次有序地消费数据,只有上一个消费者消费完后,下一个消费者才能消费数据,
 * 下一个消费者是谁都可以,但要保证这些消费者线程拿到的数据是有顺序的
 */
public class Demo2 {
	public static void main(String[] args) throws Exception {
		// 定义一个信号量,用于让一次只有一个线程执行
		Semaphore semaphore = new Semaphore(1);
		// 定义一个同步队列
		BlockingQueue<String> queue = new SynchronousQueue<String>();
		for(int i = 0 ; i < 10 ; i++){
			new Thread(){
				@Override
				public void run() {
					try {
						// 获取信号量
						semaphore.acquire();
						String input = queue.take();
						String output = TestDo.doSome(input);
						System.out.println(Thread.currentThread().getName()+ ":" + output);
						// 释放信号量
						semaphore.release();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				};
			}.start();
		}
		System.out.println("begin:"+(System.currentTimeMillis()/1000));
		for(int i=0;i<10;i++){  //这行不能改动
			String input = i+"";  //这行不能改动
			// String output = TestDo.doSome(input);
			queue.put(input);
		}
	}
}

//不能改动此TestDo类
class TestDo {
	public static String doSome(String input){
		
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		String output = input + ":"+ (System.currentTimeMillis() / 1000);
		return output;
	}
}

4. 现有程序同时启动了4个线程去调用TestDo.doSome(key, value)方法,由于TestDo.doSome(key, value)方法内的代码是先暂停1秒,然后再输出以秒为单位的当前时间值,所以,会打印出4个相同的时间值,如下所示:
        4:4:1258199615
        1:1:1258199615
        3:3:1258199615
        1:2:1258199615
        请修改代码,如果有几个线程调用TestDo.doSome(key, value)方法时,传递进去的key相等(equals比较为true),则这几个线程应互斥排队输出结果,即当有两个线程的key都是"1"时,它们中的一个要比另外其他线程晚1秒输出结果,如下所示:
        4:4:1258199615
        1:1:1258199615
        3:3:1258199615
        1:2:1258199616
      总之,当每个线程中指定的key相等时,这些相等key的线程应每隔一秒依次输出时间值(要用互斥),如果key不同,则并行执行(相互之间不互斥)

我的解题方法:

package com.bjc.demo3;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

//不能改动此Test类	
	public class Test extends Thread{
		
		private TestDo testDo;
		private String key;
		private String value;
		
		public Test(String key,String key2,String value){
			this.testDo = TestDo.getInstance();
			/*常量"1"和"1"是同一个对象,下面这行代码就是要用"1"+""的方式产生新的对象,
			以实现内容没有改变,仍然相等(都还为"1"),但对象却不再是同一个的效果*/
			this.key = key+key2; 
			this.value = value;
		}


		public static void main(String[] args) throws InterruptedException{
			Test a = new Test("1","","1");
			Test b = new Test("1","","2");
			Test c = new Test("3","","3");
			Test d = new Test("4","","4");
			System.out.println("begin:"+(System.currentTimeMillis()/1000));
			a.start();
			b.start();
			c.start();
			d.start();

		}
		
		public void run(){
			testDo.doSome(key, value);
		}
	}

	class TestDo {

		private TestDo() {}
		private static TestDo _instance = new TestDo();	
		public static TestDo getInstance() {
			return _instance;
		}
		
		private List<Object> list = new ArrayList<Object>();
		public void doSome(Object key, String value) {
			// 注意这里不能仅仅加synchronized同步,因为每次传递过来的key都是不同的对象,所以需要处理
			Object o = key;
			if(!list.contains(o)){
				list.add(key);
			} else {
				// 这里不能用list.indexOf(o)来获取对象的下标,因为多线程的原因,集合的大小会改变
				// 所以这里用迭代器
				while(list.iterator().hasNext()){
					try {
						Thread.sleep(30);
						Object o1 = list.iterator().next();
						if(o1.equals(o)){
							o = o1;
							break;
						}
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
			synchronized (o) 
			// 以大括号内的是需要局部同步的代码,不能改动!
			{
				try {
					Thread.sleep(1000);
					System.out.println(key+":"+value + ":"
							+ (System.currentTimeMillis() / 1000));
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}

	}

标准答案:

package syn;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;

//不能改动此Test类	
public class Test extends Thread{
	
	private TestDo testDo;
	private String key;
	private String value;
	
	public Test(String key,String key2,String value){
		this.testDo = TestDo.getInstance();
		/*常量"1"和"1"是同一个对象,下面这行代码就是要用"1"+""的方式产生新的对象,
		以实现内容没有改变,仍然相等(都还为"1"),但对象却不再是同一个的效果*/
		this.key = key+key2; 
/*		a = "1"+"";
		b = "1"+""
*/
		this.value = value;
	}


	public static void main(String[] args) throws InterruptedException{
		Test a = new Test("1","","1");
		Test b = new Test("1","","2");
		Test c = new Test("3","","3");
		Test d = new Test("4","","4");
		System.out.println("begin:"+(System.currentTimeMillis()/1000));
		a.start();
		b.start();
		c.start();
		d.start();

	}
	
	public void run(){
		testDo.doSome(key, value);
	}
}

class TestDo {

	private TestDo() {}
	private static TestDo _instance = new TestDo();	
	public static TestDo getInstance() {
		return _instance;
	}

	//private ArrayList keys = new ArrayList();
	private CopyOnWriteArrayList keys = new CopyOnWriteArrayList();
	public void doSome(Object key, String value) {
		Object o = key;
		if(!keys.contains(o)){
			keys.add(o);
		}else{

			for(Iterator iter=keys.iterator();iter.hasNext();){
				try {
					Thread.sleep(20);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				Object oo = iter.next();
				if(oo.equals(o)){
					o = oo;
					break;
				}
			}
		}
		synchronized(o)
		// 以大括号内的是需要局部同步的代码,不能改动!
		{
			try {
				Thread.sleep(1000);
				System.out.println(key+":"+value + ":"
						+ (System.currentTimeMillis() / 1000));
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

}

注意:标准答案使用的是CopyOnWriteArrayList并发容器

2. 并发集合

未完待续。。。。。。

发布了128 篇原创文章 · 获赞 6 · 访问量 3223

猜你喜欢

转载自blog.csdn.net/weixin_43318134/article/details/103811792