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