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

单例模式

定义:一个类在系统中只产生一个实例
优点:对于频繁使用的对象,可以省略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 queue;
    private static AtomicInteger count = new AtomicInteger();
    private static final int SLEEPTIME = 1000;
    public Producer(BlockingQueue 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 queue;
    private static final int SLEEPTIME = 1000;
    public Consumer(BlockingQueue 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 queue = new LinkedBlockingQueue(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进行数据保护.

实战java高并发程序设计第五章--并行模式与算法_第1张图片

代码: 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 {
    @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 {
    public PCData newInstance(){
        return new PCData();
    }
}

//生产者
public class Producer {
    private final RingBuffer ringBuffer;
    public Producer(RingBuffer 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 disruptor = new Disruptor(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 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的优化:解决伪共享问题

cpu的高速缓存: 读写数据最小单位为缓存行,它从主存赋值到缓存的最小单位,一般为32字节到128字节
伪共享问题: 当两个变量放在一个缓存行时,在多线程访问中,可能会影响彼此的性能.假如变量X和Y在同一个缓存行,运行在CPU1上的新城更新了X,那么cpu2上的缓存行就会失效,,同一行的Y即使没有修改也会变成无效,导致cache无法命中.
解决方案:为避免此类问题,需要把X的前后空间都占据一定的饿位置(padding,作填充用)

实战java高并发程序设计第五章--并行模式与算法_第2张图片
实战java高并发程序设计第五章--并行模式与算法_第3张图片

代码:
public final class FalseSharing implements Runnable {
    public final static int NUM_THREADS = 8; // 此处填入逻辑处理器数量,本机为4核8线程
    public final static long ITERATIONS = 500L * 1000L * 1000L;
    private final int arrayIndex;
    private static VolatileLong[] longs = new VolatileLong[NUM_THREADS];
    static {
        for (int i = 0; i < longs.length; i++) {
            longs[i] = new VolatileLong();
        }
    }
    public FalseSharing(final int arrayIndex) {
        this.arrayIndex = arrayIndex;
    }
    public static void main(final String[] args) throws Exception {
        final long start = System.currentTimeMillis();
        runTest();
        System.out.println("duration = " + (System.currentTimeMillis() - start));
    }
    private static void runTest() throws InterruptedException {
        Thread[] threads = new Thread[NUM_THREADS];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(new FalseSharing(i));
        }
        for (Thread t : threads) {
            t.start();
        }
        for (Thread t : threads) {
            t.join();
        }
    }
    public void run() {
        long i = ITERATIONS + 1;
        while (0 != --i) {
            longs[arrayIndex].value = i;
        }
    }
    //JDK 7 某些版本 和 JDK 8中 会把不用的数据优化 导致 这种优化手段失效
    // Unlock: -XX:-RestrictContended (JDK 8  option)
    //@sun.misc.Contended 
    public final static class VolatileLong {
        public volatile long value = 0L;    //8个字节
        public long p1, p2, p3, p4, p5, p6, p7 = 8L; // 56个字节,此处是作为padding而存在
    }
}

//输出结果: 3801
//当注释掉p1, p2, p3, p4, p5, p6, p7时  结果为23038,明显时间长很多
Dispruptor框架充分考虑到了这个问题,核心组件Sequence会被频繁访问(每次入队,Sequence+1),其结构如下:
class LhsPadding {
    protected long p1, p2, p3, p4, p5, p6, p7;
}
class Value extends LhsPadding {
    protected volatile long value;
}
class RhsPadding extends Value {
    protected long p9, p10, p11, p12, p13, p14, p15;
}
public class Sequence extends RhsPadding {
}            //Sequence主要使用的value,LhsPadding和RhsPadding在这个value前后安置了一些占位空间,使得value可以无冲突地存在于缓存中.
注意:
RingBuffer中实际产生的数组打下是缓冲区实际大小再加上两倍的BUFFER_PAD.

Future模式

Future模式是多线程开发中非常常见的一种设计模式,核心思想是异步调用

Future模式的简单实现

核心接口Data: 客户端希望获得的数据.
RealData : 真实数据,最终希望获得的数据
FutureData : 提取RealData的凭证,可立刻返回
public interface Data {
    public String getResult();
}

public class FutureData implements Data {
    protected RealData realdata = null;
    protected boolean isReady = false;
    public synchronized void setRealData(RealData realdata) {
        if (isReady) {
            return;
        }
        this.realdata = realdata;
        isReady = true;
        notifyAll();        //等realdata注入完后,通知getresult()方法
    }
    public synchronized String getResult() {    //等待realdata构造完成
        while (!isReady) {
            try {
                wait();        //当调用result时,为准备好数据时阻塞住线程
            } catch (InterruptedException e) {
            }
        }
        return realdata.result;
    }
}

public class RealData implements Data {
    protected final String result;
    public RealData(String para) {
        //RealData的构造可能很慢,需要用户等待很久
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < 10; i++) {
            sb.append(para);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
            }
        }
        result = sb.toString();
    }
    public String getResult() {
        return result;
    }
}

public class Client {
    public Data request(final String queryStr) {
        final FutureData future = new FutureData();
        // RealData的构建很慢
        new Thread() {                                      
            public void run() {                             
                RealData realdata = new RealData(queryStr);
                future.setRealData(realdata);
            }                                               
        }.start();
        return future;
    }
}

public class Main {
    public static void main(String[] args) {
        Client client = new Client();
        Data data = client.request("a");
        System.out.println("请求完毕");
        try {
            //这里可以用一个sleep代替了对其它业务逻辑的处理
            Thread.sleep(2000);
        } catch (InterruptedException e) {
        }
            //使用真实的数据
        System.out.println("数据 = " + data.getResult());
    }
}

JDK中的Future

        ExecutorService executorService = Executors.newFixedThreadPool(10);
        Future future = executorService.submit(() -> {
            try {
                Thread.currentThread().sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
                Thread.currentThread().interrupt();
            }
        });
        future.get();    //需要等待任务完成,get()会阻塞住
        System.out.println("处理完毕");

Guava对Future的支持

public class FutrueDemo2 {
    public static void main(String args[]) throws InterruptedException {
        ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10));
        ListenableFuture task = service.submit(new RealData("x"));
        Futures.addCallback(task, new FutureCallback() {
            public void onSuccess(String o) {
                System.out.println("异步处理成功,result=" + o);
            }
            public void onFailure(Throwable throwable) {        //对异常的处理
                System.out.println("异步处理失败,e=" + throwable);
            }
        }, MoreExecutors.newDirectExecutorService());
        System.out.println("main task done.....");
        Thread.sleep(3000);
    }
}

并行流水线

现在要生产一批小玩偶.小玩偶的制作分为四个步骤:第一,组装身体;第二,在身体上安装四肢和头部;第三,给组装完成的玩偶穿上一件漂亮的衣服;第四,包装出 货。为了加快制作进度,我们不可能叫四个人同时加工一个玩具,因为这四个步骤有着严 重的依赖关系。如果没有身体,就没有地方安装四肢;如果没有组装完成,就不能穿衣服; 如果没有穿上衣服,就不能包装发货。因此,找四个人来做一个玩偶是毫无意义的。
但是,如果你现在要制作的不是1个玩偶,而是1万个玩偶,那情况就不同了。你可以找四个人,第一个人只负责组装身体,完成后交给第二个人;第二个人只负责安装头部 和四肢,完成后交付第三人;第三人只负责穿衣服,完成后交付第四人:第四人只负责包 装发货。这样所有人都可以一起工作,共同完成任务,而整个时间周期也能缩短到原来的 14左右,这就是流水线的思想。一旦流水线满载,每次只需要一步(假设一个玩偶需要四 步)就可以产生一个玩偶

实战java高并发程序设计第五章--并行模式与算法_第4张图片

在多核或者分布式场景中,这种设计思路可以有效地将有依赖关系的操作分配在不同的线程中进行计算,尽可能利用多核优势.

并行搜索

    static int[] arr = { 5, 52, 6, 3, 4, 10, 8, 100, 35, 78, 64, 31, 77, 90,
            45, 53, 89, 78, 1,2 };
    static ExecutorService pool = Executors.newCachedThreadPool();
    static final int Thread_Num=2;
    static AtomicInteger result=new AtomicInteger(-1);
    public static int search(int searchValue,int beginPos,int endPos){
        int i=0;
        for(i=beginPos;i=0){
                return result.get();
            }
            if(arr[i] == searchValue){
                //如果设置失败,表示其它线程已经先找到了
                if(!result.compareAndSet(-1, i)){
                    return result.get();
                }
                return i;
            }
        }
        return -1; 
    }

网络NIO

传统BIO一个请求对应一个线程进行处理,倾向于让cpu进行io等待,oos的read()等都是阻塞等待IO,效率低下

实战java高并发程序设计第五章--并行模式与算法_第5张图片

public class MultiThreadEchoServer {
    private static ExecutorService  tp=Executors.newCachedThreadPool();
    static class HandleMsg implements Runnable{
        Socket clientSocket;
        public HandleMsg(Socket clientSocket){
            this.clientSocket=clientSocket;
        }
        public void run(){
            BufferedReader is =null;
            PrintWriter os = null;
            try {
                is = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
                os = new PrintWriter(clientSocket.getOutputStream(), true);
                // 从InputStream当中读取客户端所发送的数据  
                String inputLine = null;
                long b=System.currentTimeMillis();
                while ((inputLine = is.readLine()) != null) {
                    os.println(inputLine);
                }
                long e=System.currentTimeMillis();
                System.out.println("spend:"+(e-b)+"ms");
            } catch (IOException e) {
                e.printStackTrace();
            }finally{
                try {
                    if(is!=null)is.close();
                    if(os!=null)os.close();
                    clientSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public static void main(String args[]) {
        ServerSocket echoServer = null;
        Socket clientSocket = null;
        try {
            echoServer = new ServerSocket(8000);
        } catch (IOException e) {
            System.out.println(e);
        }
        while (true) {
            try {
                clientSocket = echoServer.accept();
                System.out.println(clientSocket.getRemoteSocketAddress() + " connect!");
                tp.execute(new HandleMsg(clientSocket));
            } catch (IOException e) {
                System.out.println(e);
            }
        }
    }
}
NIO三大组件: Channel+Buffer+Selector
Channel:channel对应socket,一个连接一个channel.
Buffer:简单理解成内存区域或者byte数组.
Selector:一个selector可以管理多个channel,当channel中无数据时,selector则会处于等待状态,当channel中准备好数据时,selector立即获得通知,进行出来

实战java高并发程序设计第五章--并行模式与算法_第6张图片

    private void startServer() throws Exception {
        selector = SelectorProvider.provider().openSelector();
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);        //设置为非阻塞模式
        InetSocketAddress isa = new InetSocketAddress(8000);
        ssc.socket().bind(isa);
        SelectionKey acceptKey = ssc.register(selector, SelectionKey.OP_ACCEPT);   //将selector绑定到serversocketchannel上
        for (;;) {
            selector.select();    //阻塞方法
            Set readyKeys = selector.selectedKeys();
            Iterator i = readyKeys.iterator();
            long e=0;
            while (i.hasNext()) {    //轮询事件
                SelectionKey sk = (SelectionKey) i.next();
                i.remove();    //处理完就删除掉,避免重复使用
                if (sk.isAcceptable()) {
                    doAccept(sk);
                }
                else if (sk.isValid() && sk.isReadable()) {
                    if(!time_stat.containsKey(((SocketChannel)sk.channel()).socket()))
                        time_stat.put(((SocketChannel)sk.channel()).socket(), 
                            System.currentTimeMillis());
                    doRead(sk);
                }
                else if (sk.isValid() && sk.isWritable()) {
                    doWrite(sk);
                    e=System.currentTimeMillis();
                    long b=time_stat.remove(((SocketChannel)sk.channel()).socket());
                    System.out.println("spend:"+(e-b)+"ms");
                }
            }
        }
    }
    private void doAccept(SelectionKey sk) {
        ServerSocketChannel server = (ServerSocketChannel) sk.channel();
        SocketChannel clientChannel;
        try {
            clientChannel = server.accept(); //客户端通信通道
            clientChannel.configureBlocking(false);
            SelectionKey clientKey = clientChannel.register(selector, SelectionKey.OP_READ);    //注册到selector上,表示对read事件感兴趣
            EchoClient echoClient = new EchoClient();
            clientKey.attach(echoClient); //将客户端负载到selectionKey上,这样连接的处理过程中,可以共享这个实例.
            InetAddress clientAddress = clientChannel.socket().getInetAddress();
            System.out.println("Accepted connection from " + clientAddress.getHostAddress() + ".");
        } catch (Exception e) {
            System.out.println("Failed to accept new client.");
            e.printStackTrace();
        }
    }
    private void doRead(SelectionKey sk) {
        SocketChannel channel = (SocketChannel) sk.channel();
        ByteBuffer bb = ByteBuffer.allocate(8192); //数据缓冲区8K
        int len;
        try {
            len = channel.read(bb);
            if (len < 0) {
                disconnect(sk);
                return;
            }
        } catch (Exception e) {
            System.out.println("Failed to read from client.");
            e.printStackTrace();
            disconnect(sk);
            return;
        }
        // Flip the buffer.
        bb.flip();
        tp.execute(new HandleMsg(sk,bb));
    }
    private void doWrite(SelectionKey sk) {
        SocketChannel channel = (SocketChannel) sk.channel();
        EchoClient echoClient = (EchoClient) sk.attachment();
        LinkedList outq = echoClient.getOutputQueue();
        ByteBuffer bb = outq.getLast();
        try {
            int len = channel.write(bb);
            if (len == -1) {
                disconnect(sk);
                return;
            }
            if (bb.remaining() == 0) {
                // The buffer was completely written, remove it.
                outq.removeLast();
            }
        } catch (Exception e) {
            System.out.println("Failed to write to client.");
            e.printStackTrace();
            disconnect(sk);
        }
        if (outq.size() == 0) {
            sk.interestOps(SelectionKey.OP_READ);    //当全部数据发送完成后,移除write事件
        }
    }

你可能感兴趣的:(java,高并发)