1 下列属于线程安全类的是:

A.StringBuffer

B.StringBuilder

C.ArrayList

D.HashMap

参考答案

本题正确答案为A。

StringBuffer属于线程安全的类;StringBuilder、ArrayList、HashMap是非线程安全类。

2 使用BlockingQueue实现生产者和消费者模型

参考答案

在工作中,可能会碰到这样一种情况:某个模块负责产生数据,这些数据由另一个模块来负责处理。产生数据的模块,则形象地称为生产者;而处理数据的模块,则称为消费者。在生产者与消费者之间在加个缓冲区,我们形象的称之为仓库,生产者负责往仓库了进商品,而消费者负责从仓库里拿商品,这就构成了生产者消费者模式。生产者和消费者模型的结构如图-1所示。

图-1

在实际工作中,典型的生产者消费者模型的案例是流媒体在线现在播放。流媒体下载是生产者;流媒体播放是消费者,如图-2所示。

图-2

以下案例使用BlockingQueue实现生产者和消费者模型,模拟了流媒体下载视频数据和播放视频的过程。

实现此案例需要按照如下步骤进行。

步骤一:创建生产者

首先新建Donwload类,在该类中模拟视频数据下载的过程,其中BlockingQueue对象则是在上文中提到的缓冲区,Donwload对象负责向该缓冲区存储数据,代码如下所示:

import java.util.concurrent.BlockingQueue;

public class Donwload implements Runnable {
	private final BlockingQueue<Object> queue;
	public Donwload(BlockingQueue<Object> q) {
		queue = q;
	}
	public void run() {
		try {
			while (true) {
				System.out.println("下载视频数据"+index);
				queue.put(produce());				
			}
		} catch (InterruptedException ex) {
		}
	}
	int index=0;
	public Object produce() {
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return "视频数据"+index++;
	}
}

步骤二:创建消费者

新建类Player,在该类中实现模拟播放视频的过程,其中BlockingQueue对象也是在上文中提到的缓冲区,Player对象负责从该缓冲区中取数据,代码如下所示:

import java.util.concurrent.BlockingQueue;

public class Player implements Runnable  {
	private final BlockingQueue<Object> queue;
	public Player(BlockingQueue<Object> q) {
		queue = q;
	}
	public void run() {
		try {
			while (true) {
				consume(queue.take());
			}
		} catch (InterruptedException ex) {
		}
	}
	void consume(Object x) {
		System.out.println("播放"+x);
	}
}

步骤三:启动线程

首先新建Setup类,在该类的main方法中,创建缓冲区BlockingQueue对象并将该对象传给下载器和播放器,这样就保证了下载器和播放器使用了相同的缓冲区。代码如下所示:

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

public class Setup {
	public static void main(String[] args) {
		BlockingQueue<Object> q = new ArrayBlockingQueue<Object>(10);
		Donwload p = new Donwload(q);
		Player c1 = new Player(q);
		Player c2 = new Player(q);
		new Thread(p).start();
		new Thread(c1).start();
		new Thread(c2).start();
	}
}

从上述代码中,可以看出有一个线程负责下载,两个线程负责播放。

步骤四:运行

运行上述代码,由于程序在不断的运行,所以图-3是截取控制台的部分数据。

图-3

从图-3的输出结果可以看出,只有下载数据完成后该数据才能播放,这是因为,BlockingQueue内部使用两条队列,可允许两个线程同时向队列一个做存储,一个做取出操作。如果BlockingQueue对象是空的,则从BlockingQueue对象取数据的操作将会被阻塞进入等待状态,直到BlockingQueue对象有数据进入则被唤醒。同样,如果BlockingQueue对象是满的,任何试图向其存数据的操作也会被阻塞进入等待状态,直到BlockingQueue对象内有空间则会被唤醒继续操作,这样,BlockingQueue对象保证并发安全的同时提高了队列的存取效率。