实战java高并发程序设计第五章--并行模式与算法(一)

本文深入探讨了单例模式的定义、优点及其实现方式,包括饿汉式、懒汉式和静态内部类式。同时,详细解析了生产者消费者模式的原理,包括基于阻塞队列和无锁实现的Disruptor框架,以及不同消费者响应时间策略的优缺点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在这里插入图片描述

单例模式

定义:一个类在系统中只产生一个实例
优点:对于频繁使用的对象,可以省略new的时间,对于重量级对象来说是一比客观的系统性能提升
 内存使用频率低,减少GC次数,缩短GC停顿时间
//饿汉式 类第一次使用时必定会创建单例对象
public class Singleton {
 public static int STATUS=1;
 private Singleton(){
 System.out.println("Singleton is create");
 }
 private static Singleton instance = new Singleton();
 public static Singleton getInstance() {
 return instance;
 } 
}
//懒汉式 并发环境下,锁竞争激烈则性能较差
public class LazySingleton {
 private LazySingleton() {
 System.out.println("LazySingleton is create"); 
 }
 private static LazySingleton instance = null;
 public static synchronized LazySingleton getInstance() {
 if (instance == null)
 instance = new LazySingleton();
 return instance;
 }
}
//静态内部类式 //集合上述两种优势
public class StaticSingleton {
 public static int STATUS;
 private StaticSingleton(){
 System.out.println("StaticSingleton is create");
 }
 private static class SingletonHolder {
 private static StaticSingleton instance = new StaticSingleton();
 }
 public static StaticSingleton getInstance() {
 return SingletonHolder.instance;
 }
}
注意:
另外还有一种双重检查模式来创建单例,这种模式丑陋且复杂,甚至在低版本中不能保证正确性,不推荐使用

不变模式

定义:一个对象一旦被创建,内部状态永远不发生改变,故永远为线程安全的
使用场景:对象创建后,内部状态和数据不再发生变化
对象需要被共享,被多线程频繁访问

public final class PCData { //父类不变,子类也必须不变,但无法保证这一点,故使用final
 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;
 }
}
注意:
String 类也是不变模式,保证了在多线程下的性能

生产者消费模式

定义:生产者负责提交用户请求,消费者负责具体处理生产者提交的任务.生产者和消费者之间通过共享内存缓冲区进行通信.
特点:对生产者线程和消费者线程进行解耦,优化系统整体结构,环节性能瓶颈对系统性能的影响
//共享数据模型
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;
 }
}
//生产者
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;
 }
}
//消费者
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();
 }
 }
}
//main方法
public class PCMain {
 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();
 }
}

生产者消费模式:无锁实现

上述BlockingQueue基于锁和阻塞等待来实现线程同步,在高并发环境下性能有限.
ConcurrentLinkedQueue是一个高性能的队列,基于CAS来实现生产者消费者模式.
CAS编程相对比较困难,我们可以使用Disruptor框架来实现.

无锁缓存框架Disruptor

特点:环形队列,队列大小需要事先指定,无法动态扩展,数组大小需设置为2的整数次方.内存复用度高,减少GC等消耗
结构:RingBuffer的结构在写入和读取的操作时,均使用CAS进行数据保护.

在这里插入图片描述

代码: disruptor使用的是disruptor-3.3.2
//数据模型
public class PCData {
 private long value;
 public void set(long value)
 {
 this.value = value;
 }
 public long get(){
 return value;
 }
}
//消费者 需要事先WorkHandler接口
public class Consumer implements WorkHandler<PCData> {
 @Override
 public void onEvent(PCData event) throws Exception { //框架的回调方法
 System.out.println(Thread.currentThread().getId() + ":Event: --"
 + event.get() * event.get() + "--");
 }
}
//数据对象工厂类,Disruptor框架初始化时构造所有对象实例
public class PCDataFactory implements EventFactory<PCData> {
 public PCData newInstance(){
 return new PCData();
 }
}
//生产者
public class Producer {
 private final RingBuffer<PCData> ringBuffer;
 public Producer(RingBuffer<PCData> ringBuffer) {
 this.ringBuffer = ringBuffer;
 }
 public void pushData(ByteBuffer bb) { //提取bytebuffer中的数据,装载到环形数组中
 long sequence = ringBuffer.next(); // 获取下一个序列号
 try {
 PCData event = ringBuffer.get(sequence); // 取得环形数组中的PCdata
 event.set(bb.getLong(0)); // 设置pcdata
 } finally {
 ringBuffer.publish(sequence); //必须发布,只有发布后的数据才能被消费者看见
 }
 }
}
//main函数
public class PCMain {
 public static void main(String[] args) throws Exception {
 Executor executor = Executors.newCachedThreadPool();
 PCDataFactory factory = new PCDataFactory();
 // Specify the size of the ring buffer, must be power of 2.
 int bufferSize = 1024; //缓冲区设置 为2^10
 Disruptor<PCData> disruptor = new Disruptor<PCData>(factory, //创建disruptor对象
 bufferSize,
 executor,
 ProducerType.MULTI,
 new BlockingWaitStrategy()
 );
 // Connect the handler
// disruptor.handleEventsWith(new LongEventHandler());
 disruptor.handleEventsWithWorkerPool( //用于消费者,设置4个消费者,并把每个消费者映射到一个线程中
 new Consumer(),
 new Consumer(),
 new Consumer(),
 new Consumer());
 disruptor.start(); //启动并初始化disruptor系统
 RingBuffer<PCData> ringBuffer = disruptor.getRingBuffer();
 Producer producer = new Producer(ringBuffer);
 ByteBuffer bb = ByteBuffer.allocate(8);
 for (long l = 0; true; l++) { //生产者不断地写入缓冲区
 bb.putLong(0, l);
 producer.pushData(bb);
 Thread.sleep(100);
 System.out.println("add data " + l);
 }
 }
}

消费者响应时间的策略

消费者如何监控缓冲区中的信息呢,以下给出了4种策略,这些策略由WaitStrategy接口进行封装

  1. BlcokingWaitStrategy: 默认策略,类似BlockingQueue,利用锁和条件(Condition)进行数据的监控和线程的唤醒, 最节省CPU,但高并发下性能最差
  2. SleepingWaitStrategy: 与上述类似.它会在循环中不断等待,先进行自旋等待,若不成功,则会使用Thread.yield()方法让出cpu,并最终使用LockSupport.parkNanos(1)进行线程休眠.因此这个策略对数据处理可能会产生较高的平均延迟,适合对延时要求不高的场合,且对生产者线程的影响最小.典型场景为异步日志.
  3. YieldingWaitingStrategy: 这个策略用于低延时的场合。消费者线程会不断循环监控缓冲区的变化,在循环内部,它会使用Thread.yield方法让出CPU给别的线程执行时间。如果你需要一个高性能的系统,并且对延时有较为严格的要求,则可以考虑这种策略。使用这种策略时,相当于消费者线程变成了一个内部执行了Thread.yield方法的死循环。因此,你最好有多于消费者线程数量的逻辑CPU数量(这里的逻辑CPU指的是“双核四线程”中的那个四线程,否则,整个应用程序恐怕都会受到影响).
  4. BusySpinWaitStrategy : 这个是最疯狂的等待策略了。它就是一个死循环!消费者线程会尽最大努力疯狂监控缓冲区的变化。因此,它会吃掉所有的CPU资源。只有对延迟非常苛刻的场合可以考虑使用它(或者说,你的系统真的非常繁忙)。因为使用它等于开启了一个死循环监控,所以你的物理CPU数量必须要大于消费者的线程数。注意,我这里说的是物理CPU,如果你在一个物理核上使用超线程技术模拟两个逻辑核,另外一个逻辑核显然会受到这种超密集计算的影响而不能正常工作。

cpu cache的优化:解决伪共享问题

待续…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值