实战Java高并发程序设计(五、并行模式与算法)

5.1单例模式

单例模式:是一种常用的软件设计模式,在它的核心结构中值包含一个被称为单例的特殊类。一个类只有一个实例,即一个类只有一个对象实例。

 对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;售票时,一共有100张票,可有有多个窗口同时售票,但需要保证不要超售(这里的票数余量就是单例,售票涉及到多线程)。如果不是用机制对窗口对象进行唯一化将弹出多个窗口,如果这些窗口显示的都是相同的内容,重复创建就会浪费资源。

1.双重检查

public class Singleton {
    private static volatile Singleton singleton;
    private Singleton(){}

    public static Singleton getInstacne(){
        if (singleton == null){
            synchronized (Singleton.class){
                if (singleton == null){
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

2.静态内部类

public class Singleton {
    private Singleton(){}

    private static class SingletonInstance{
        private static final Singleton INSTANCE = new Singleton();
    }
    public static Singleton getInstance(){
        return SingletonInstance.INSTANCE;
    }
}

5.2不变模式

不变模式的核心思想是一个对象一旦被创建,则它的内部状态将永远不会发生改变。 

比如一个对象的存活时间,它会随时间的变化而变化,因此它是只读属性,而不是不变属性。

不变模式的主要应用场景:

  1. 当对象创建后,其内部状态和数据不再发生变化
  2. 对象需要被共享,被多线程频繁访问
public final class Product {
    private final String no;
    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;
    }
}

注意:不变模式通过回避问题而不是解决问题的态度来处理 多线程并发访问控制。不变对象是不需要进行同步控制的。由于并发同步会对性能产生不良的影响,因此,在需求允许的情况下,不变模式可以提高系统的并发性能和并发量。

5.3生产者消费者模式

在生产者与消费者之间建立一个缓冲区,对生产者线程和消费者线程进行解耦。

5.4Future模式

先想一个场景:假如你突然想做饭,但是没有厨具,也没有食材。网上购买厨具比较方便,食材去超市买更放心。

实现分析:在快递员送厨具的期间,我们肯定不会闲着,可以去超市买食材。所以,在主线程里面另起一个子线程去网购厨具。

public class test{
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        long startTime = System.currentTimeMillis();
        // 第一步 网购厨具
        Callable<Cooker> onlineShopping = new Callable<Cooker>() {
            @Override
            public Cooker call() throws Exception {
                System.out.println("第一步:下单");
                System.out.println("第一步:等待送货");
                // 模拟送货时间
                Thread.sleep(5000);
                System.out.println("第一步:快递送到");
                return new Cooker();
            }

        };
        FutureTask<Cooker> task = new FutureTask<Cooker>(onlineShopping);
        new Thread(task).start();
        // 第二步 去超市购买食材
//        模拟购买食材时间
        Thread.sleep(2000);
        Food food = new Food();
        System.out.println("第二步:食材到位");
//        第三步 用厨具烹饪食材
//        联系快递员,询问是否到货
        if (!task.isDone()) {
            System.out.println("第三步:厨具还没到,心情好就等着(心情不好就调用cancel方法取消订单)");
        }
        Cooker cooker = task.get();
        System.out.println("第三步:厨具到位,开始展现厨艺");
        cook(cooker, food);
        System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms");
    }

    /**
     * 厨具
     */
    static class Cooker{}

    /**
     * 食物
     */
    static class Food{}

    /**
     * 烹饪
     */
    static void cook(Cooker cooker,Food food){}
}

5.5并行搜索

public class test {
    /**
     * 定义我们需要查询的无序数组
     */
    static int[] arr = {1, 22, 2, 3, 4, 5, 344, 6, 7, 8, 10, 9};
    /**
     * 定义线程池数据,已经存放结果的Result
     */
    static ExecutorService pool = Executors.newCachedThreadPool();
    static final int thread_num = 2;
    /**
     * 初始值定位-1
     */
    static AtomicInteger result = new AtomicInteger(-1);

    public static int search(int searchValue, int beginPos, int endPos) {
        for (int j = beginPos; j < endPos; j++) {
            if (result.get() >= 0) {
                return result.get();
            }
            if (arr[j] == searchValue) {
                //如果设置失败,表示其他现场已经先找到了
                if (!result.compareAndSet(-1, j)) {
                    //如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。
                    //-1当前值为-1就返回
                    return result.get();
                }
                return j;
            }
        }
        return -1;
    }

    public static class SearchTask implements Callable<Integer> {
        int begin, end, searchValue;

        public SearchTask(int searchValue, int begin, int end) {
            this.begin = begin;
            this.end = end;
            this.searchValue = searchValue;
        }

        @Override
        public Integer call() {
            int re = search(searchValue, begin, end);
            return re;
        }
    }

    public static int psearch(int searchValue) throws InterruptedException, ExecutionException {
        int subArrSize = arr.length / thread_num + 1;
        List<Future<Integer>> re = new ArrayList<Future<Integer>>();
        for (int i = 0; i < arr.length; i += subArrSize) {
            int end = i + subArrSize;
            if (end >= arr.length) {
                end = arr.length;
            }
            re.add(pool.submit(new SearchTask(searchValue, i, end)));
        }
        for (Future<Integer> fu :
                re) {
            if (fu.get() >= 0) {
                return (fu.get());
            }

        }
        return -1;
    }

    public static void main(String[] args) {
        try {
            System.out.println(psearch(10));
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

5.6NIO

NIO主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector。传统IO基于字节流和字符流进行操作,而NIO基于Channel和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择区)用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个线程可以监听多个数据通道。

传统io与nio区别:

public class test {
    public static void method1() {
        RandomAccessFile aFile = null;
        try {
            aFile = new RandomAccessFile("src/test.txt", "rw");
            FileChannel fileChannel = aFile.getChannel();
            ByteBuffer buf = ByteBuffer.allocate(1024);
            int bytesRead = fileChannel.read(buf);
            System.out.println(bytesRead);
            while (bytesRead != -1) {
                buf.flip();
                while (buf.hasRemaining()) {
                    System.out.print((char) buf.get());
                }
                buf.compact();
                bytesRead = fileChannel.read(buf);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (aFile != null) {
                    aFile.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void method2() {
        InputStream in = null;
        try {
            in = new BufferedInputStream(new FileInputStream("src/test.txt"));
            byte[] buf = new byte[1024];
            int bytesRead = in.read(buf);
            while (bytesRead != -1) {
                for (int i = 0; i < bytesRead; i++) {
                    System.out.print((char) buf[i]);
                }
                bytesRead = in.read(buf);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
//        method2();
        method1();
    }
}
  1. BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中。比如:文件的上传下载
  2. NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂
  3. AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK7开始支持。 

猜你喜欢

转载自blog.csdn.net/qq_33283652/article/details/83113718