4.1.4 不变模式
不变模式天生就是多线程友好的,它的核心思想是,一个对象一旦被创建,则它的内部状态将永远不会发生改变。所以,没有一个线程可以修改其内部状态和数据,同时其内部状态也绝不会自行发生改变。基于这个特性,对不变对象的多线程操作将不需要进行同步控制。
△ 注意:不变模式和只读属性是有一定区别的。不变模式比只读属性具有更强的一致性和不变性。对只读属性的对象而言,对象本身不能被其它线程修改,但是对象的自身状态却可能自行修改。
比如,一个对象的存活时间(对象的创建时间和当前时间的时间差)是只读的,因为任何一个第三方线程都不能修改这个属性,但是这是一个可变的属性,因为随着时间的推移,它的值会发生改变。而不变模式要求,无论出于什么原因,对象自创建后,其内部状态和数据保持绝对的稳定。
综上所述,不变模式的主要使用场景主要需要满足以下两个条件:
□当对象创建后,其内部状态和数据不再发生任何变化。
□ 对象需要被共享,被多线程频繁访问。
在Java语言中,不变模式的实现很简单。为确保对象创建后,不发生任何改变,并保证不变模式正常工作,只需要注意一下4点:
□去除setter方法以及所有修改自身属性的方法
□将所有属性设置为私有,并用final标记,确保其不可修改
□确保没有子类可以重载修改它的行为
□有一个可以创建完整对象的构造函数。
public final class Product {// final确保无子类
private final String no;// 私有属性,不会被其它对象修改,final确保属性不会被2次修改
private final String name;
private final double price;
// 在创建对象后, 需指定数据,因为创建后, 无法进行修改
public Product(String no, String name, double price) {
super();
this.no = no;
this.name = name;
this.price = price;
}
public String getNo() {
return no;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
}
在不变模式的实现中,final关键字起到了重要的作用。对class的final定义保证了不变类没有子类,确保其所有的getter不会被修改。对属性的final定义确保所有的数据只能在对象被构造时赋值1次,之后,永远不会发生改变。
JDK中,不变模式的使用非常广泛。其中,最为典型的就是java.lang.String类。此外,所有元数据类的包装类,都是使用不变模式实现的。由于基本数据类型和String类型在实际的软件开发中应用极其广泛,使用不变模式后,所有的实例的方法均不需要进行同步操作,保证了他们在多线程环境下的性能。
△ 注意:不变模式通过回避问题而不是解决问题的态度来处理多线程并发访问空置。不变对象是不需要进行同步操作的。由于并发同步会对性能产生不良的影响,因此,在需求允许的情况下,不变模式可以提高系统的并发性能和并发量。
4.1.5 生产者-消费者模式
生产者-消费者模式是一个经典的多线程设计模式,它为多线程间的协作提供了良好的解决方案。在生产者-消费者模式中,通常有两类线程,即若干个生产者线程和若干个消费者线程。生产者线程负责提交用户请求,消费者线程则负责具体处理生产者提交的任务。生产者和消费者通过共享的缓冲区进行通信。
△ 注意:生产者-消费者模式中的内存缓冲区的主要功能是数据在多线程间的共享。此外,通过该缓冲区,可以缓解生产者和消费者间的性能差。缓冲区是生产者和消费者的通信桥梁,避免了生产者和消费者之间直接通信,从而降低了它们之间的耦合。生产者不需要知道消费者的存在,消费者也不需要知道生产者的存在。
同时,由于内存缓冲区的存在,允许生产者和消费者在执行速度上存在时间差,无论是生产者在某一局部时间内速度高于消费者,或者消费者在局部时间内高于生产者,都可以通过共享内存缓冲区得到缓解,确保系统正常运行。
代码如下:BlockingQueue充当了共享内存缓冲区,用于维护任务或数据队列(PCData对象)。PCData对象表示一个生产任务,或者相关任务的数据。生产者对象和消费者对象均引用同一个BlockingQueue实例。生产者负责创建PCData对象,并将它加入BlockingQueue中,消费者则从BlockingQueue队列中获取数据。
public class Producer implements Runnable{
private volatile boolean isRunning = true;
private BlockingDeque<PCData> queue;// 内存缓冲区
private static AtomicInteger count = new AtomicInteger();// 总数,原子操作
private static final int SLEEPTIME = 1000;
public Producer(
BlockingDeque<PCData> queue) {
this.queue = queue;
}
@Override
public void run() {
PCData data = null;
Random r = new Random();
System.out.println("start producer id="+Thread.currentThread().getId());
try {
while(isRunning) {
Thread.sleep(r.nextInt(SLEEPTIME));
data = new PCData(count.incrementAndGet());// 构造任务数据
System.out.println(data + " is put into queue");
if(!queue.offer(data, 2, TimeUnit.SECONDS)) {// 提交到缓冲区中
System.out.println("failed to put data:" + data);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupted();
}
}
public void stop() {
isRunning = false;
}
}
public class Consumer implements Runnable{
private BlockingDeque<PCData> queue;// 缓冲区
private static final int SLEEPTIME = 1000;
public Consumer(
BlockingDeque<PCData> queue) {
this.queue = queue;
}
@Override
public void run() {
System.out.println("start Consumer id="+Thread.currentThread().getId());
Random r = new Random();// 随机等待时间
try {
while(true) {
PCData data = queue.take();// 提取任务
if(null != data) {
int d = data.getData();
int re = d * d;// 计算平方
System.out.println(MessageFormat.format("{0}*{1}={2}", d, d, re));
Thread.sleep(r.nextInt(SLEEPTIME));
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
//任务相关的数据
public class PCData {
private final int intData;// 数据
public PCData(int intData) {
this.intData = intData;
}
public PCData(String str) {
this.intData = Integer.valueOf(str);
}
public int getData() {
return intData;
}
@Override
public String toString() {
return "PCData{" +
"intData=" + intData +
'}';
}
public static void main(String[] args) throws InterruptedException{
// 建立缓冲区
BlockingDeque<PCData> queue = new LinkedBlockingDeque<PCData>(10);
// 建立生产者
Producer producer1 = new Producer(queue);
Producer producer2 = new Producer(queue);
Producer producer3 = new Producer(queue);
Producer producer4 = new Producer(queue);
// 建立消费者
Consumer consumer1 = new Consumer(queue);
Consumer consumer2 = new Consumer(queue);
Consumer consumer3 = new Consumer(queue);
Consumer consumer4 = new Consumer(queue);
ExecutorService service = Executors.newCachedThreadPool();// 建立线程池
// 运行生产者
service.execute(producer1);
service.execute(producer2);
service.execute(producer3);
service.execute(producer4);
// 运行消费者
service.execute(consumer1);
service.execute(consumer2);
service.execute(consumer3);
service.execute(consumer4);
Thread.sleep(10 * 1000);
// 停止生产者
producer1.stop();
producer2.stop();
producer3.stop();
producer4.stop();
Thread.sleep(4000);
service.shutdown();
}
}