不变模式
定义
- 一个对象一旦被创建,则它的内部状态将永远不会发生改变。
特点
- 因为不变模式的对象他的内部状态(数据)永远不会变化,所以其天生就是对多线程友好。
- 没有一个线程可以修改其内部状态和数据,同时其内部状态也绝不会自行发生改变。
基于这些特性,对不变对象的多线程操作不需要进行同步控制。
在多线程中的优势
- 节省了多线程之间同步数据对于系统性能的消耗。
- 依靠对象的不变性,可以确保其在没有同步操作的多线程环境中依然始终保持内部状态的一致性和正确性。
不变模式和只读属性
- 不变模式是比只读属性具有更强的一致性和不变性。
- 对只读属性的对象而言,对象本身不能被其他线程修改,但是对象的自身状态却可能自行修改。
使用场景
- 当对象创建后,其内部状态和数据不再发生任何变化。
- 对象需要被共享,被多线程频繁访问。
不变模式的实现
- 去除setter方法以及所有修改自身属性的方法。
- 将所有属性设置为私有,并用final标记,确保其不可修改。
- 确保没有子类可以重载修改它的行为。
- 有一个可以创建完整对象的构造函数。
例子
public final class Product { //确保无子类
private final String no; //私有属性,不会被其他对象获取
ic final class Product { //确保无子类
private final String no; //私有属性,不会被其他对象获取
private final String name; //final保证属性不会被2次赋值
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关键字起到了重要的作用。对属性的final定义确保所有数据只能在对象被构造时赋值1次。之后,就永远不再发生改变。而对class的final确保了类不会有子类。根据里氏代换原则,子类可以完全的替代父类。如果父类是不变的,那么子类也必须是不变的,但实际上我们并无法约束这点,为了防止子类做出一些意外的行为,这里就干脆把子类都禁用了。
JDK中的不变模式
所有的元数据类包装类,都是使用不变模式实现的。主要的不变模式类型如下:
- java.lang.String
- java.lang.Boolean
- java.lang.Byte
- java.lang.Character
- java.lang.Double
- java.lang.Float
- java.lang.Integer
- java.lang. Long
- java.lang.Short
由于基本数据类型和String类型在实际的软件开发中应用极其广泛,使用不变模式后,所有实例的方法均不需要进行同步操作,保证了它们在多线程环境下的性能。
注意:
<font color=red>不变模式通过回避问题而不是解决问题的态度来处理多线程并发访问控制</font>。
不变对象是不需要进行同步操作的。
由于并发同步会对性能产生不良的影响,因此,在需求允许的情况下,不变模式可以提高系统的并发性能和并发量。
生产者-消费者模式
生产者-消费者模式是一个经典的多线程设计模式,它为多线程间的协作提供了良好的解决方案。在生产者-消费者模式中,通常有两类线程,即若干个生产者线程和若干个消费者线程。生产者线程负责提交用户请求,消费者线程则负责具体处理生产者提交的任务。生产者和消费者之间则通过共享内存缓冲区进行通信。
共享内存缓冲区
生产者-消费者模式的核心组件是共享内存缓存区,它作为生产者和消费者间的通信桥梁,避免了生产者和消费者的直接通信,从而将生产者和消费者进行解耦。生产者不需要知道消费者的存在,消费者也不需要知道生产者的存在。
同时,由于内存缓冲区的存在,允许生产者和消费者在执行速度上存在时间差,无论是生产者在某一局部时间内速度高于消费者,还是消费者在局部时间内高于生产者,都可以通过共享内存缓冲区得到缓解,确保系统正常运行。
功能
-
生产者-消费者模式中的内存缓存区的主要功能是数据在多线程间的共享。
-
通过该缓冲区,可以缓解生产者和消费者间的性能差。
生产者-消费者角色模式
角色 | 作用 |
---|---|
生产者 | 用于提交用户请求,提取用户任务,并装入内存缓冲区 |
消费者 | 在内存缓冲区中提取并处理任务 |
内存缓冲区 | 缓存生产者提交的任务或数据,提供消费者使用 |
任务 | 声称这项内存缓冲区提交的数据结构 |
Main | 使用生产者和消费者的客户端 |
例子
首先,生产者线程的实现如下,它构建PCData对象,并放入BlockingQueue队列中
public class Producer implements Runnable {
private volatile boolean isRunning = true;
private BlockingQueue<PCData> queue; //内存缓冲区
private static AtomicInteger count = new AtomicInteger(); //总数,原子操作
private static final int SLEEPTIME = 1000;
public Producer(BlockingQueue<PCData> queue) {
this.queue = queue;
}
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.err.println("failed to put data:" + data);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
public void stop() {
isRunning = false;
}
}
对应的消费者的实现如下。它从BlockingQueue队列中取出PCData对象,并进行相应的计算。
public class Consumer implements Runnable {
private BlockingQueue<PCData> queue; //缓冲区
private static final int SLEEPTIME = 1000;
public Consumer(BlockingQueue<PCData> queue) {
this.queue = queue;
}
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 re = data.getData() * data.getData(); //计算平方
System.out.println(MessageFormat.format("{0}*{1}={2}",
data.getData(), data.getData(), re));
Thread.sleep(r.nextInt(SLEEPTIME));
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
}
PCData作为生产者和消费者之间的共享数据模型,定义如下:
public final class PCData { //任务相关的数据
private final int intData; //数据
public PCData(int d){
intData=d;
}
public PCData(String d){
intData=Integer.valueOf(d);
}
public int getData(){
return intData;
}
@Override
public String toString(){
return "data:"+intData;
}
}
在主函数中,创建三个生产者和三个消费者,并让它们协作运行。在主函数的实现中,定义LinkedBlockingQueue作为BlockingQueue的实现类。
public class Main {
public static void main(String[] args) throws InterruptedException {
//建立缓冲区
BlockingQueue<PCData> queue = new LinkedBlockingQueue<PCData>(10);
Producer producer1 = new Producer(queue); //建立生产者
Producer producer2 = new Producer(queue);
Producer producer3 = new Producer(queue);
Consumer consumer1 = new Consumer(queue); //建立消费者
Consumer consumer2 = new Consumer(queue);
Consumer consumer3 = new Consumer(queue);
ExecutorService service = Executors.newCachedThreadPool(); //建立线程池
service.execute(producer1); //运行生产者
service.execute(producer2);
service.execute(producer3);
service.execute(consumer1); //运行消费者
service.execute(consumer2);
service.execute(consumer3);
Thread.sleep(10 * 1000);
producer1.stop(); //停止生产者
producer2.stop();
producer3.stop();
Thread.sleep(3000);
service.shutdown();
}
注意:
生产者-消费者模式很好地对生产者线程和消费者线程进行解耦,优化了系统整体结构。
同时,由于缓冲区的作用,允许生产者线程和消费者线程存在执行上的性能差异,从<font color=red>一定程度</font>上缓解了性能瓶颈对系统性能的影响。
网友评论