美文网首页
《实战java高并发程序设计》笔记(五)

《实战java高并发程序设计》笔记(五)

作者: MikeShine | 来源:发表于2020-02-22 14:56 被阅读0次

写在前面

上一章中我们对于 java 锁的一些优化和注意事项进行了学习,内容并不多,并且在之前的进阶课程中已经有了相关的了解。在第4章中就是进行了一些更细节的理解。


第5章 并行模式与算法

由于并行程序的设计比串行设计要复杂的多,所以,在这一章,作者向我们介绍了一下并行模式下的常见的设计方法。


并行模式与算法知识结构

1. 单例模式

对单例模式的细致讲解

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当您想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数是私有的。

  • 是一种对象创建模式,用于产生一个对象的具体实例,确保一个类只能产生一个实例
  • 对于频繁使用的对象,可以省略 new 操作花费的时间(检查,分配内存,初始化),减少系统开销
  • new 的少,系统内存使用频率降低,减轻 GC 压力,缩短 GC 时间。
  • 构造函数私有, instance 对象(实例对象) 私有且静态

1.1 单例模式代码
从代码角度,将单例模式分为饿汉式和懒汉式:
饿汉式:在类加载时就完成了初始化,创建了对象
懒汉式:在类第一次使用才完成初始化,创建对象。
1.2 饿汉式

public class Singleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
        return instance;  
    }  
}

饿汉式在类加载过程中就完成了实例化,所以避免了多线程同步问题,是线程安全的。
缺点就是,如果实力没有用,会造成内存浪费。
1.3 普通懒汉式

public class Singleton {

    private static Singleton instance = null;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

这个是懒汉式最简单的写法,只有在方法第一次被访问时候才会实例化,达到了 lazy loading 效果。但是这个方法,不是线程安全的。例如,在还没有实例化的时候,两个线程同时访问,就会出现多次实例化。
1.4 同步方法懒汉式

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
    public static synchronized Singleton getInstance() {  
    if (instance == null) {  
        instance = new Singleton();  
    }  
    return instance;  
    }  
}

对实例化方法进行了加锁 sync 处理。是线程安全。
但是,是对整个方法进行了同步,实际上这个方法只需要执行一次就可完成实例化,每次线程访问都同步,显然效率低下
1.5 双重检查懒汉式

public class Singleton {

    private static volatile Singleton instance;

    private Singleton() {}

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

}

这个写法用了两个 if 判断,double-check。并且同步的是代码块,效率高,是对上面同步方法懒汉式的改进。
还是上面提到的场景,还没有实例化,但是两个线程同时访问了静态方法,同时运行到第一个 if,这时候线程A先进入同步代码块来完成实例化,结束后,B再进入,但是由于第二个 if ,实际上 B 的实例化是不能完成的。

1.6 单例模式优缺点
优点:单例类只有一个实例,节省内存。对于频繁创建销毁的对象,使用单例类可以提高系统性能。
同时可以设置全局访问点,共享数据。例如 Web 页面计数器就可以用单例模式实现技术值的保存
缺点:没有借口,不能扩展单例类。


2. 不变模式

核心:一旦一个对象被创建,则它内部状态将永远不发生改变。
实现只要做到4点:

  • 去除所有的 setter 方法以及所有修改自身属性的方法
  • 属性设为 private & final,确保其不被其他访问,并且不可变
  • 确保没有子类可以重载修改它的行为
  • 有一个可以创建完整对象的构造函数

使用不变模式的例子:String 类

不变模式是用了回避问题的方法,而不是解决的态度,来处理并发访问控制。不变对象是不需要进行同步操作的。因此在系统允许情况下,不变模式可以提高系统的并发能力。

看一个例子,实现了一个不变的产品对象,拥有序列号、名称和价格三个属性

/**
 * 不变模式的一个例子
 * 实现了不变的产品对象,拥有序列号、名称和价格
 */

// 为了满足没有子类可以修改,我们
// 干脆将这个类设置成了不可继承 final 类
public final class Product {
    private final String no;    //  属性设置为 private & final
    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;
    }
}

3. 生产者消费者模式

  • 生产者线程负责提交用户请求,消费者线程负责处理生产者提交的任务,二者之间通过共享内存缓冲进行通信
  • 二者之间的内存缓冲区,避免了二者直接通信,从而将二者进行解耦合
  • 缓冲区的主要功能是 数据在多线程间的共享,此外通过缓冲区可以缓解二者之间性能的差异。

这里书上还讲了一下 ,用无锁的CAS方式来实现 消费者生产者。这里先不看了。


4. Future模式

  • Future 模式的核心是 异步调用,可以被调用者立即返回。调用者可以先处理其他任务
  • RunnableFuture 作为 JDK中的Future 模式,继承了 Future 和 Runnable 两个接口,其中 run() 方法用于构造真实数据。其有一个真实的实现类 FutureTask 类(之前我们提到过,包装 Callable 的实现类对象,进而创建多线程)。FutureTask 类内部 run 方法调用 Callable 接口的 call 方法。

下面我们看一个例子

/**
 * JDK 内置 Future 模式的使用例子
 *  这个类模拟构造真实数据的过程
 */
public class RealData implements Callable<String> {
    private String para;
    public RealData(String para){
        this.para = para;
    }

    @Override
    public String call() throws Exception{
        StringBuilder sb = new StringBuilder();
        for(int i=0;i<10;i++){
            sb.append(para);
            try {
                Thread.sleep(100);
            }catch (InterruptedException e){}
        }
        return sb.toString();
    }
}
/**
 *  主函数测试
 */
public class FutureMain {
    public static void main(String args[]) throws InterruptedException,ExecutionException {
        // 构造 FutureTask 实例
        FutureTask<String> future = new FutureTask<String>(new RealData("a"));
        ExecutorService executor = Executors.newFixedThreadPool(1);
        // 执行 FutureTask,发送请求
        executor.submit(future);

        System.out.println("请求完毕");
        try{
            // 这里模拟可以做额外的操作
            Thread.sleep(200);
        }catch (InterruptedException e){}
        // 在处理完别的事情之后,回来拿真实数据
        System.out.println("数据 = "+future.get());
        executor.shutdown();
    }
}

5. 并行流水线

将不同的步骤交给不同的线程去处理,就是并行流水线。
书上给了一个例子,我们计算 (B+C)*B/2,这个数据之间是有前后联系的,所以就分别把 3 个步骤,给3个线程做,最终最后一个线程求的结果就是需要的结果


后面的几个并行排序什么的就牵扯到算法了,这里先不看了。

相关文章

网友评论

      本文标题:《实战java高并发程序设计》笔记(五)

      本文链接:https://www.haomeiwen.com/subject/clulqhtx.html