程序员shengfq

技术博客


  • Home

  • Archives

java8Lambda详解

Posted on 2020-06-07

###什么是 lambda?

#####百度百科
Lambda 表达式(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。Lambda表达式可以表示闭包(注意和数学传统意义上的不同)。

###lambda 的优点

  1. 没有使用Lambda表达式之前

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class OldThread {
    public static void main(String[] args) {
    new Thread(new Runnable() {
    public void run() {
    System.out.println("Hello World!");
    }
    }).start();
    }
    }
  2. 使用了Lambda表达式之后

    1
    2
    3
    4
    5
    public class LambdaThread {
    public static void main(String[] args) {
    new Thread(() -> System.out.println("Hello World!")).start();
    }
    }

我们发现使用lambda只需一行代码就搞定了之前需要一个匿名类需要完成的事情,所以,Lambda 表达式是创建匿名内部类的语法糖。在编译器的帮助下,可以让开发人员用更少的代码来完成工作。

###lambda 的语法风格

#####Lambda表达式由三部分组成:
参数列表

箭头

主体

有两种风格,分别是:

#####表达式-风格
(parameters) -> expression

#####块-风格
(parameters) -> { statements; }


依据上面的风格介绍,来试着判断下面给出的示例是否有效:


  • () -> {}

  • () -> “Apple”

  • () -> { return “Apple”; }

  • (Integer i) -> return “Apple” + i

  • (String s) -> { “Apple”; }

解析:

  • (1)是块风格,没有语句;
  • (2)是表达式风格,一个字符串表达式;
  • (3)是块风格,有花括号和返回语句;
  • (4)非有效,写了返回语句,但缺少花括号,补上花括号和分号,为块风格,而去掉return则为表达式风格;
  • (5)非有效,”Apple”是一个字符串表达式,不是一个语句,加上return,或者去掉分号和花括号。
  • ###函数式编程接口
    Lambda表达式写好了,我们要知道哪里能用Lambda表达式。已知Lambda表达式可看作是匿名内部类的实现,那对于匿名内部类来说最重要的是类所实现的接口,而Lambda表达式是否可用于所有接口?答案“不是的”,Lambda表达式对接口有一定的要求,必须是函数式接口。

    所谓的函数式接口指的是只定义一个抽象方法的接口
    例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public interface Comparator<T> {
    int compare(T o1, T o2);
    }

    public interface Runnable {
    void run();
    }
    public interface Callable<V> {
    V call() throws Exception;
    }

    ###复合Lambda表达式

    参考:https://github.com/alibaba/fastjson/wiki

    阿里巴巴Fastjson详解

    Posted on 2020-03-14

    ###什么是 Fastjson?
    阿里官方给的定义是, fastjson 是阿里巴巴的开源JSON解析库,它可以解析 JSON 格式的字符串,支持将 Java Bean 序列化为 JSON 字符串,也可以从 JSON 字符串反序列化到 JavaBean。

    ###Fastjson 的优点

    ####速度快
    fastjson相对其他JSON库的特点是快,从2011年fastjson发布1.1.x版本之后,其性能从未被其他Java实现的JSON库超越。

    ####使用广泛
    fastjson在阿里巴巴大规模使用,在数万台服务器上部署,fastjson在业界被广泛接受。在2012年被开源中国评选为最受欢迎的国产开源软件之一。

    ####测试完备
    fastjson有非常多的testcase,在1.2.11版本中,testcase超过3321个。每次发布都会进行回归测试,保证质量稳定。

    ####使用简单
    fastjson的 API 十分简洁。

    ####功能完备
    支持泛型,支持流处理超大文本,支持枚举,支持序列化和反序列化扩展。

    ###怎么获得 Fastjson
    你可以通过如下地方下载fastjson:

    1. maven中央仓库:[地址] (http://central.maven.org/maven2/com/alibaba/fastjson/)
    2. 在maven项目的pom文件中直接配置fastjson依赖
      fastjson最新版本都会发布到maven中央仓库,你可以直接依赖。
    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>x.x.x</version>
    </dependency>

    其中x.x.x是版本号,根据需要使用特定版本,建议使用最新版本。

    Fastjson 主要的API

    Fastjson入口类是 com.alibaba.fastjson.JSON,主要的 API 是 JSON.toJSONString 和 parseObject。

    1
    2
    3
    4
    5
    6
    7
    package com.alibaba.fastjson;
    public abstract class JSON {
    // Java对象转换为JSON字符串
    public static final String toJSONString(Object object);
    //JSON字符串转换为Java对象
    public static final <T> T parseObject(String text, Class<T> clazz, Feature... features);
    }

    序列化:

    1
    String jsonString = JSON.toJSONString(obj);

    反序列化:

    1
    VO vo = JSON.parseObject("...", VO.class);

    泛型反序列化:

    1
    2
    3
    import com.alibaba.fastjson.TypeReference;

    List<VO> list = JSON.parseObject("...", new TypeReference<List<VO>>() {});

    ###Fastjson 的性能
    fastjson是目前java语言中最快的json库,比自称最快的jackson速度还要快,第三方独立测试结果看这里:这里

    自行做性能测试时,需关闭循环引用检测的功能。

    1
    2
    JSON.toJSONString(obj, SerializerFeature.DisableCircularReferenceDetect)
    VO vo = JSON.parseObject("...", VO.class, Feature.DisableCircularReferenceDetect)

    另外,Fastjson 比 Gson 快大约6倍,测试结果可以看这里:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    Checking correctness…
    [done]
    Pre-warmup… java-built-in hessian kryo protostuff-runtime avro-generic msgpack json/jackson/databind json/jackson/databind-strings json/jackson/db-afterburner json/google-gson/databind json/svenson-databind json/flexjson/databind json/fastjson/databind smile/jackson/databind smile/jackson/db-afterburner smile/protostuff-runtime bson/jackson/databind xml/xstream+c xml/jackson/databind-aalto
    [done]
    pre. create ser deser shal +deep total size +dfl
    java-built-in 63 5523 27765 28084 28162 33686 889 514
    hessian 64 3776 6459 6505 6690 10466 501 313
    kryo 63 809 962 937 1001 1810 214 133
    protostuff-runtime 62 671 903 920 957 1627 241 151
    avro-generic 436 1234 1122 1416 1760 2994 221 133
    msgpack 61 789 1369 1385 1449 2238 233 146
    json/jackson/databind 60 1772 3089 3113 3246 5018 485 261
    json/jackson/databind-strings 64 2346 3739 3791 3921 6267 485 261
    json/jackson/db-afterburner 64 1482 2220 2233 2323 3805 485 261
    json/google-gson/databind 64 7076 4894 4962 5000 12076 486 259
    json/svenson-databind 64 5422 12387 12569 12468 17890 495 266
    json/flexjson/databind 62 20923 26853 26873 27272 48195 503 273
    json/fastjson/databind 63 1250 1208 1206 1247 2497 486 262
    smile/jackson/databind 60 1697 2117 2290 2298 3996 338 241
    smile/jackson/db-afterburner 60 1300 1614 1648 1703 3003 352 252
    smile/protostuff-runtime 61 1275 1612 1638 1685 2961 335 235
    bson/jackson/databind 63 5151 6729 6977 6918 12069 506 286
    xml/xstreamc 62 6358 13208 13319 13516 19874 487 244
    xml/jackson/databind-aalto 62 2955 5332 5465 5584 8539 683 286

    ###Fastjson 使用示例
    我们创建一个班级的对象,和一个学生对象如下:

    班级对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class Grade {

    private Long id;
    private String name;
    private List<Student> users = new ArrayList<Student>();

    // 省略 setter、getter

    public void addStudent(Student student) {
    users.add(student);
    }

    @Override
    public String toString() {
    return "Grade{" +
    "id=" + id +
    ", name='" + name + '\'' +
    ", users=" + users +
    '}';
    }
    }

    学生对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public class Student {

    private Long id;
    private String name;

    // 省略 setter、getter

    @Override
    public String toString() {
    return "Student{" +
    "id=" + id +
    ", name='" + name + '\'' +
    '}';
    }
    }

    运行的 Main 函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    public class MainTest {

    public static void main(String[] args) {
    Grade group = new Grade();
    group.setId(0L);
    group.setName("admin");

    Student student = new Student();
    student.setId(2L);
    student.setName("guest");

    Student rootUser = new Student();
    rootUser.setId(3L);
    rootUser.setName("root");

    group.addStudent(student);
    group.addStudent(rootUser);

    // 转换为 JSON
    String jsonString = JSON.toJSONString(group);
    System.out.println("JSON字符串:" + jsonString);

    // 转换为 对象BEAN
    Grade grade = JSON.parseObject(jsonString, Grade.class);
    System.out.println("JavaBean对象:" + grade);
    }
    }

    最后的运行结果如下:

    1
    2
    3
    4
    5
    JSON字符串:
    {"id":0,"name":"admin","users":[{"id":2,"name":"guest"},{"id":3,"name":"root"}]}

    JavaBean对象:
    Grade{id=0, name='admin', users=[Student{id=2, name='guest'}, Student{id=3, name='root'}]}

    ###将对象中的空值输出
    在fastjson中,缺省是不输出空值的。无论Map中的null和对象属性中的null,序列化的时候都会被忽略不输出,这样会减少产生文本的大小。但如果需要输出空值怎么做呢?

    如果你需要输出空值,需要使用
    SerializerFeature.WriteMapNullValue

    1
    2
    Model obj = ...;
    JSON.toJSONString(obj, SerializerFeature.WriteMapNullValue);

    几种空值特别处理方式:

    |SerializerFeature |描述|
    |WriteNullListAsEmpty |: 将Collection类型字段的字段空值输出为[]:|
    |WriteNullStringAsEmpty |将字符串类型字段的空值输出为空字符串 “”
    |WriteNullNumberAsZero |将数值类型字段的空值输出为0|
    |WriteNullBooleanAsFalse| 将Boolean类型字段的空值输出为false|

    具体的示例参考如下,可以同时选择多个:

    1
    2
    3
    4
    5
    6
    7
    class Model {
    public List<Objec> items;
    }

    Model obj = ....;

    String text = JSON.toJSONString(obj, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteNullListAsEmpty);

    ###Fastjson 处理日期
    Fastjson 处理日期的API很简单,例如:

    1
    JSON.toJSONStringWithDateFormat(date, "yyyy-MM-dd HH:mm:ss.SSS")

    使用ISO-8601日期格式

    1
    JSON.toJSONString(obj, SerializerFeature.UseISO8601DateFormat);

    全局修改日期格式

    1
    2
    JSON.DEFFAULT_DATE_FORMAT = "yyyy-MM-dd";
    JSON.toJSONString(obj, SerializerFeature.WriteDateUseDateFormat);

    反序列化能够自动识别如下日期格式:

    • ISO-8601日期格式
    • yyyy-MM-dd
    • yyyy-MM-dd HH:mm:ss
    • yyyy-MM-dd HH:mm:ss.SSS
    • 毫秒数字
    • 毫秒数字字符串
    • .NET JSON日期格式
    • new Date(198293238)
      虽然上面处理了单个的日期类型和全局的日期类型格式的配置,但是有时候我们需要的是对象中个别的日期类型差异化,并不一定是同一种格式的。那如何处理呢?接下来介绍 Fastjson 的定制序列化。

    ###Fastjson 定制序列化

    ####简介
    fastjson支持多种方式定制序列化。

    • 通过@JSONField定制序列化
    • 通过@JSONType定制序列化
    • 通过SerializeFilter定制序列化
    • 通过ParseProcess定制反序列化
      ####使用@JSONField配置
      ####1.JSONField 注解介绍
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package com.alibaba.fastjson.annotation;

    public @interface JSONField {
    // 配置序列化和反序列化的顺序,1.1.42版本之后才支持
    int ordinal() default 0;

    // 指定字段的名称
    String name() default "";

    // 指定字段的格式,对日期格式有用
    String format() default "";

    // 是否序列化
    boolean serialize() default true;

    // 是否反序列化
    boolean deserialize() default true;
    }

    ####2.JSONField配置方式
    可以把@JSONField配置在字段或者getter/setter方法上,例如:

    配置在字段上

    1
    2
    3
    4
    5
    6
    7
    public class VO {
    @JSONField(name="ID")
    private int id;

    @JSONField(name="birthday",format="yyyy-MM-dd")
    public Date date;
    }

    配置在 Getter/Setter 上

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class VO {
    private int id;

    @JSONField(name="ID")
    public int getId() { return id;}

    @JSONField(name="ID")
    public void setId(int id) {this.id = id;}
    }

    注意:若属性是私有的,必须有set*方法。否则无法反序列化。

    3、使用format配置日期格式化

    1
    2
    3
    4
    5
    6
    可以定制化配置各个日期字段的格式化
    public class A {
    // 配置date序列化和反序列使用yyyyMMdd日期格式
    @JSONField(format="yyyyMMdd")
    public Date date;
    }

    4、使用serialize/deserialize指定字段不序列化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class A {
    @JSONField(serialize=false)
    public Date date;
    }

    public class A {
    @JSONField(deserialize=false)
    public Date date;
    }

    5、使用ordinal指定字段的顺序
    缺省Fastjson序列化一个java bean,是根据fieldName的字母序进行序列化的,你可以通过ordinal指定字段的顺序。这个特性需要1.1.42以上版本。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public static class VO {
    @JSONField(ordinal = 3)
    private int f0;

    @JSONField(ordinal = 2)
    private int f1;

    @JSONField(ordinal = 1)
    private int f2;
    }

    6、使用serializeUsing制定属性的序列化类
    在fastjson 1.2.16版本之后,JSONField支持新的定制化配置serializeUsing,可以单独对某一个类的某个属性定制序列化,比如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public static class Model {
    @JSONField(serializeUsing = ModelValueSerializer.class)
    public int value;
    }

    public static class ModelValueSerializer implements ObjectSerializer {
    @Override
    public void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType,
    int features) throws IOException {
    Integer value = (Integer) object;
    String text = value + "元";
    serializer.write(text);
    }
    }

    测试代码

    1
    2
    3
    4
    Model model = new Model();
    model.value = 100;
    String json = JSON.toJSONString(model);
    Assert.assertEquals("{\"value\":\"100元\"}", json);

    ####|3使用@JSONType配置
    和JSONField类似,但JSONType配置在类上,而不是field或者getter/setter方法上。

    ####|4通过SerializeFilter定制序列化
    1、简介

    • SerializeFilter是通过编程扩展的方式定制序列化。fastjson支持6种SerializeFilter,用于不同场景的定制序列化
    • PropertyPreFilter 根据PropertyName判断是否序列化
    • PropertyFilter 根据PropertyName和PropertyValue来判断是否序列化
    • NameFilter 修改Key,如果需要修改Key,process返回值则可
    • ValueFilter 修改Value
    • BeforeFilter 序列化时在最前添加内容
    • AfterFilter 序列化时在最后添加内容
      2、PropertyFilter 根据PropertyName和PropertyValue来判断是否序列化
      1
      2
      3
      public interface PropertyFilter extends SerializeFilter {
      boolean apply(Object object, String propertyName, Object propertyValue);
      }

    可以通过扩展实现根据object或者属性名称或者属性值进行判断是否需要序列化。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    PropertyFilter filter = new PropertyFilter() {

    public boolean apply(Object source, String name, Object value) {
    if ("id".equals(name)) {
    int id = ((Integer) value).intValue();
    return id >= 100;
    }
    return false;
    }
    };
    JSON.toJSONString(obj, filter); // 序列化的时候传入filter

    3、PropertyPreFilter 根据PropertyName判断是否序列化

    和PropertyFilter不同只根据object和name进行判断,在调用getter之前,这样避免了getter调用可能存在的异常。

    1
    2
    3
    public interface PropertyPreFilter extends SerializeFilter {
    boolean apply(JSONSerializer serializer, Object object, String name);
    }

    4、NameFilter 序列化时修改Key

    如果需要修改Key,process返回值则可

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public interface NameFilter extends SerializeFilter {
    String process(Object object, String propertyName, Object propertyValue);
    }
    fastjson内置一个PascalNameFilter,用于输出将首字符大写的Pascal风格。 例如:

    import com.alibaba.fastjson.serializer.PascalNameFilter;

    Object obj = ...;
    String jsonStr = JSON.toJSONString(obj, new PascalNameFilter());

    5、ValueFilter 序列化时修改Value

    1
    2
    3
    public interface ValueFilter extends SerializeFilter {
    Object process(Object object, String propertyName, Object propertyValue);
    }

    6、BeforeFilter 序列化时在最前添加内容
    在序列化对象的所有属性之前执行某些操作,例如调用 writeKeyValue 添加内容

    1
    2
    3
    4
    5
    public abstract class BeforeFilter implements SerializeFilter {
    protected final void writeKeyValue(String key, Object value) { ... }
    // 需要实现的抽象方法,在实现中调用writeKeyValue添加内容
    public abstract void writeBefore(Object object);
    }

    7、AfterFilter 序列化时在最后添加内容
    在序列化对象的所有属性之后执行某些操作,例如调用 writeKeyValue 添加内容

    1
    2
    3
    4
    5
     public abstract class AfterFilter implements SerializeFilter {
    protected final void writeKeyValue(String key, Object value) { ... }
    // 需要实现的抽象方法,在实现中调用writeKeyValue添加内容
    public abstract void writeAfter(Object object);
    }

    ####通过ParseProcess定制反序列化
    1、简介

    • ParseProcess是编程扩展定制反序列化的接口。fastjson支持如下ParseProcess:
    • ExtraProcessor 用于处理多余的字段
    • ExtraTypeProvider 用于处理多余字段时提供类型信息

    2、使用ExtraProcessor 处理多余字段

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public static class VO {
    private int id;
    private Map<String, Object> attributes = new HashMap<String, Object>();
    public int getId() { return id; }
    public void setId(int id) { this.id = id;}
    public Map<String, Object> getAttributes() { return attributes;}
    }

    ExtraProcessor processor = new ExtraProcessor() {
    public void processExtra(Object object, String key, Object value) {
    VO vo = (VO) object;
    vo.getAttributes().put(key, value);
    }
    };

    VO vo = JSON.parseObject("{\"id\":123,\"name\":\"abc\"}", VO.class, processor);
    Assert.assertEquals(123, vo.getId());
    Assert.assertEquals("abc", vo.getAttributes().get("name"));

    3、使用ExtraTypeProvider 为多余的字段提供类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    public static class VO {
    private int id;
    private Map<String, Object> attributes = new HashMap<String, Object>();
    public int getId() { return id; }
    public void setId(int id) { this.id = id;}
    public Map<String, Object> getAttributes() { return attributes;}
    }

    class MyExtraProcessor implements ExtraProcessor, ExtraTypeProvider {
    public void processExtra(Object object, String key, Object value) {
    VO vo = (VO) object;
    vo.getAttributes().put(key, value);
    }

    public Type getExtraType(Object object, String key) {
    if ("value".equals(key)) {
    return int.class;
    }
    return null;
    }
    };
    ExtraProcessor processor = new MyExtraProcessor();

    VO vo = JSON.parseObject("{\"id\":123,\"value\":\"123456\"}", VO.class, processor);
    Assert.assertEquals(123, vo.getId());
    Assert.assertEquals(123456, vo.getAttributes().get("value")); // value本应该是字符串类型的,通过getExtraType的处理变成Integer类型了。

    ####在 Spring MVC 中集成 Fastjson

    如果你使用 Spring MVC 来构建 Web 应用并对性能有较高的要求的话,可以使用 Fastjson 提供的FastJsonHttpMessageConverter 来替换 Spring MVC 默认的 HttpMessageConverter 以提高 @RestController @ResponseBody @RequestBody 注解的 JSON序列化速度。下面是配置方式,非常简单。

    XML式
    如果是使用 XML 的方式配置 Spring MVC 的话,只需在 Spring MVC 的 XML 配置文件中加入下面配置即可

    1
    2
    3
    4
    5
    <mvc:annotation-driven>
    <mvc:message-converters>
    <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"/>
    </mvc:message-converters>
    </mvc:annotation-driven>

    通常默认配置已经可以满足大部分使用场景,如果你想对它进行自定义配置的话,你可以添加 FastJsonConfig Bean。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <mvc:annotation-driven>
    <mvc:message-converters>
    <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter">
    <property name="fastJsonConfig" ref="fastJsonConfig"/>
    </bean>
    </mvc:message-converters>
    </mvc:annotation-driven>

    <bean id="fastJsonConfig" class="com.alibaba.fastjson.support.config.FastJsonConfig">
    <!-- 自定义配置... -->
    </bean>

    编程式

    如果是使用编程的方式(通常是基于 Spring Boot 项目)配置 Spring MVC 的话只需继承 WebMvcConfigurerAdapter覆写configureMessageConverters方法即可,就像下面这样。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Configuration
    public class WebMvcConfigurer extends WebMvcConfigurerAdapter {
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
    //自定义配置...
    //FastJsonConfig config = new FastJsonConfig();
    //config.set ...
    //converter.setFastJsonConfig(config);
    converters.add(0, converter);
    }
    }

    注意:

    1. 如果你使用的 Fastjson 版本小于1.2.36的话(强烈建议使用最新版本),在与Spring MVC 4.X 版本集成时需使用 FastJsonHttpMessageConverter4。

    2. SpringBoot 2.0.1版本中加载WebMvcConfigurer的顺序发生了变动,故需使用converters.add(0, converter);指定FastJsonHttpMessageConverter在converters内的顺序,否则在SpringBoot 2.0.1及之后的版本中将优先使用Jackson处理。

    ####在 Spring Data Redis 中集成 Fastjson

    通常我们在 Spring 中使用 Redis 是通过 Spring Data Redis 提供的 RedisTemplate 来进行的,如果你准备使用 JSON 作为对象序列/反序列化的方式并对序列化速度有较高的要求的话,建议使用 Fastjson 提供的 GenericFastJsonRedisSerializer 或 FastJsonRedisSerializer 作为 RedisTemplate 的 RedisSerializer。下面是配置方式,非常简单。

    XML式
    如果是使用 XML 的方式配置 Spring Data Redis 的话,只需将 RedisTemplate 中的 Serializer 替换为 GenericFastJsonRedisSerializer 即可。

    1
    2
    3
    4
    5
    6
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
    <property name="connectionFactory" ref="jedisConnectionFactory"/>
    <property name="defaultSerializer">
    <bean class="com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer"/>
    </property>
    </bean>

    下面是完整的 Spring 集成 Redis 配置供参考。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    <!-- Redis 连接池配置(可选) -->
    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
    <property name="maxTotal" value="${redis.pool.maxActive}"/>
    <property name="maxIdle" value="${redis.pool.maxIdle}"/>
    <property name="maxWaitMillis" value="${redis.pool.maxWait}"/>
    <property name="testOnBorrow" value="${redis.pool.testOnBorrow}"/>
    <!-- 更多连接池配置...-->
    </bean>
    <!-- Redis 连接工厂配置 -->
    <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
    <!--设置连接池配置,不设置的话会使用默认的连接池配置,若想禁用连接池可设置 usePool = false -->
    <property name="poolConfig" ref="jedisPoolConfig" />
    <property name="hostName" value="${host}"/>
    <property name="port" value="${port}"/>
    <property name="password" value="${password}"/>
    <property name="database" value="${database}"/>
    <!-- 更多连接工厂配置...-->
    </bean>
    <!-- RedisTemplate 配置 -->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
    <!-- 设置 Redis 连接工厂-->
    <property name="connectionFactory" ref="jedisConnectionFactory"/>
    <!-- 设置默认 Serializer ,包含 keySerializer & valueSerializer -->
    <property name="defaultSerializer">
    <bean class="com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer"/>
    </property>
    <!-- 单独设置 keySerializer -->
    <property name="keySerializer">
    <bean class="com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer"/>
    </property>
    <!-- 单独设置 valueSerializer -->
    <property name="valueSerializer">
    <bean class="com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer"/>
    </property>
    </bean>

    编程式

    如果是使用编程的方式(通常是基于 Spring Boot 项目)配置 RedisTemplate 的话只需在你的配置类(被@Configuration注解修饰的类)中显式创建 RedisTemplate Bean,设置 Serializer 即可。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
    RedisTemplate redisTemplate = new RedisTemplate();
    redisTemplate.setConnectionFactory(redisConnectionFactory);

    GenericFastJsonRedisSerializer fastJsonRedisSerializer = new GenericFastJsonRedisSerializer();
    redisTemplate.setDefaultSerializer(fastJsonRedisSerializer);//设置默认的Serialize,包含 keySerializer & valueSerializer

    //redisTemplate.setKeySerializer(fastJsonRedisSerializer);//单独设置keySerializer
    //redisTemplate.setValueSerializer(fastJsonRedisSerializer);//单独设置valueSerializer
    return redisTemplate;
    }

    通常使用 GenericFastJsonRedisSerializer 即可满足大部分场景,如果你想定义特定类型专用的 RedisTemplate 可以使用 FastJsonRedisSerializer来代替 GenericFastJsonRedisSerializer,配置是类似的。

    参考:https://github.com/alibaba/fastjson/wiki

    消息中间件的选型

    Posted on 2019-08-24

    目录

    1.我们的业务实际情况面临的问题
    2.我们面临选择有几个要素需要考虑
    3.备选产品Kafka、ActiveMQ、RabbitMQ、RocketMQ 有什么优缺点
    4.备选的产品的优缺点

    1.我们的业务实际情况面临的问题

    1.1.我们是一家在线教育公司,核心业务有课程,网校,题库,客服,CRM.

    在618等促销节日也有高并发大流量场景.做微服务改造过程中涉及到多个异构系统需要通信,需要服务间各种调用,通知,异步解耦,也有促销高并发秒杀.为了实现这些业务场景中能保障业务稳定和系统可靠性,我们考虑使用消息中间件来解决一些业务场景中需要系统间解耦,异步集群消费,从而提高系统的QPS和RT.

    耦合带来的缺点:调用方要关注被调方是否正常,如果是事务性的,根据容错策略还得考虑各个方法的返回值,增大了业务方法开发难度和不确定性.而解耦的话,如果业务有变更(新增/删除),都可以对mq消息进行修改订阅.业务场景:发送APP推送通知,之前是通过同步调用.net的http接口,改造后通过发送mq通知就行.

    同步带来的缺点:API响应的时间是累加的,因为是单线程操作.我们团队对用户端API操作响应要控制在毫秒级内.如果通过mq,发送多条消息后立即返回,响应速度就更快.

    流量高峰期该怎么处理:通过生产者将消息堆积到mq队列,消费者按限流策略均衡平稳消费.(流量高峰期)
    每天 0:00 到 12:00,A 系统风平浪静,每秒并发请求数量就 50 个。结果每次一到8:00 ~ 1000 ,每秒并发请求数量突然会暴增到 5k+ 条。但是系统是直接基于 MySQL 的,大量的请求涌入 MySQL,每秒钟对 MySQL 执行约 5k 条 SQL。
    如果使用 MQ,每秒 5k 个请求写入 MQ,A 系统每秒钟最多处理 2k 个请求,因为 MySQL 每秒钟最多处理 2k 个。A 系统从 MQ 中慢慢拉取请求,每秒钟就拉取 2k 个请求,不要超过自己每秒能处理的最大请求数量就 ok,这样下来,哪怕是高峰期的时候,A 系统也绝对不会挂掉。而 MQ 每秒钟 5k 个请求进来,就 2k 个请求出去,结果就导致在中午高峰期(1 个小时),可能有几十万甚至几百万的请求积压在 MQ 中。

    1.2.我们面临选择有几个要素需要考虑

    1.市面上稳定可靠,延迟低,消息可靠性高不丢,具备分布式架构,避免单点故障.
    2.社区活跃度高,出现了问题能快速的解决.
    3.运维管理难度不高,学习成本低,全组内都要掌握,程序员就能管理.
    

    1.3.消息队列的缺点

    1.系统增加了一个中间件,万一中间件崩溃了,无法服务,所有依赖系统都会崩溃.
    2.增加了系统复杂度,消息在集群里传播,要考虑消息可靠性(不丢),不重复消费,消息传递的顺序性.
    3.多系统有事务场景,发送消息完本地事务结束,如何保证生产者一定发送成功,消费者一定消费成功,全局事务数据一致.
    

    2.选型的理由(需要的特性)

    2.1 broker可用性高,支持分布式集群,避免单点故障
    2.2 消息消费的顺序性,比如活动参加,先抢先得.
    2.3 消息是否可堆积,持久化.高并发场景,早高峰期间服务高可用.
    2.4 消息可靠性至少一次,不丢. 数据最终一致性,幂等性去重.
    

    3. 备选产品Kafka、ActiveMQ、RabbitMQ、RocketMQ 有什么优缺点?

    4.备选的产品的优缺点

    综合上述因素对比,结合公司的实际情况,我们选择了学习成本较低,社区活跃度高,有大量插件支持,还有监控管理工具的rabbitmq.
    RabbitMQ是一款使用Erlang语言开发的,实现AMQP(高级消息队列协议)的开源消息中间件。首先要知道一些RabbitMQ的特点,官网可查:
    可靠性。支持持久化,传输确认,发布确认等保证了MQ的可靠性。
    灵活的分发消息策略。这应该是RabbitMQ的一大特点。在消息进入MQ前由Exchange(交换机)进行路由消息。分发消息策略有:简单模式、工作队列模式、发布订阅模式、路由模式、通配符模式。
    支持集群。多台RabbitMQ服务器可以组成一个集群,形成一个逻辑Broker。
    多种协议。RabbitMQ支持多种消息队列协议,比如 STOMP、MQTT 等等。
    支持多种语言客户端。RabbitMQ几乎支持所有常用编程语言,包括 Java、.NET、Ruby 等等。
    可视化管理界面。RabbitMQ提供了一个易用的用户界面,使得用户可以监控和管理消息 Broker。
    插件机制。RabbitMQ提供了许多插件,可以通过插件进行扩展,也可以编写自己的插件。

    5.rabbitmq的4种交换机类型

    Broker:消息队列服务进程。此进程包括两个部分:Exchange和Queue。
    Exchange:消息队列交换机。按一定的规则将消息路由转发到某个队列。
    Queue:消息队列,存储消息的队列。
    Producer:消息生产者。生产方客户端将消息同交换机路由发送到队列中。
    Consumer:消息消费者。消费队列中存储的消息。

    Exchange的四种类型以及用法
    1.Direct Exchange 一对一点对点发送接收
    2.Fanout exchange 一个发送到交换机的消息都会被转发到与该交换机绑定的所有队列上 广播模式.
    3.Topic Exchange 这种交换机是使用通配符去匹配,路由到对应的队列 广播模式

    比较常用的就是以上三种:直连(DirectExchange),发布订阅(FanoutExchange),通配符(TopicExchange)。熟练运用这三种交换机类型,基本上可以解决大部分的业务场景。

    4.Headers Exchange 它的路由不是用routingKey进行路由匹配,而是在匹配请求头中所带的键值进行路由。

    (Reactor)异步非阻塞多路IO复用模型

    Posted on 2019-08-21

    异步非阻塞多路I/O复用机制
    这个名词可以理解为NIO,是比同步阻塞模型响应更加快速的线程模型,为什么要理解他?
    因为Redis,Nginx,Netty的线程模型都是在此基础上构建的.

    需要基础:Linux,JAVA IO,JAVA NIO,JAVA SOCKET,JAVA.UTIL.CONCURRENT.*;

    文章原文链接:<a href=http://www.blogjava.net/DLevin/archive/2015/09/02/427045.html>点我访问



    1. Reactor模式详解
    前记
    第一次听到Reactor模式是三年前的某个晚上,一个室友突然跑过来问我什么是Reactor模式?我上网查了一下,很多人都是给出NIO中的 Selector的例子,而且就是NIO里Selector多路复用模型,只是给它起了一个比较fancy的名字而已,虽然它引入了EventLoop概 念,这对我来说是新的概念,但是代码实现却是一样的,因而我并没有很在意这个模式。然而最近开始读Netty源码,而Reactor模式是很多介绍Netty的文章中被大肆宣传的模式,因而我再次问自己,什么是Reactor模式?本文就是对这个问题关于我的一些理解和尝试着来解答。

    2. 什么是Reactor模式
    要回答这个问题,首先当然是求助Google或Wikipedia,其中Wikipedia上说:“The reactor design pattern is an event handling pattern for handling service requests delivered concurrently by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to associated request handlers.”。从这个描述中,我们知道Reactor模式首先是事件驱动的,有一个或多个并发输入源,有一个Service Handler,有多个Request Handlers;这个Service Handler会同步的将输入的请求(Event)多路复用的分发给相应的Request Handler。如果用图来表达:

    从结构上,这有点类似生产者消费者模式,即有一个或多个生产者将事件放入一个Queue中,而一个或多个消费者主动的从这个Queue中Poll事件来处理;而Reactor模式则并没有Queue来做缓冲,每当一个Event输入到Service Handler之后,该Service Handler会主动的根据不同的Event类型将其分发给对应的Request Handler来处理。

    更学术的,这篇文章<a href=http://www.dre.vanderbilt.edu/~schmidt/PDF/reactor-siemens.pdf>(Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events)上说:

    “The Reactor design pattern handles service requests that are delivered concurrently to an application by one or more clients. Each service in an application may consistent of several methods and is represented by a separate event handler that is responsible for dispatching service-specific requests. Dispatching of event handlers is performed by an initiation dispatcher, which manages the registered event handlers. Demultiplexing of service requests is performed by a synchronous event demultiplexer. Also known as Dispatcher, Notifier”。


    这段描述和Wikipedia上的描述类似,有多个输入源,有多个不同的EventHandler(RequestHandler)来处理不同的请求,Initiation Dispatcher用于管理EventHander,EventHandler首先要注册到Initiation Dispatcher中,然后Initiation Dispatcher根据输入的Event分发给注册的EventHandler;然而Initiation Dispatcher并不监听Event的到来,这个工作交给Synchronous Event Demultiplexer来处理。
    Reactor模式结构
    在解决了什么是Reactor模式后,我们来看看Reactor模式是由什么模块构成。图是一种比较简洁形象的表现方式,因而先上一张图来表达各个模块的名称和他们之间的关系:
    <img src=http://www.blogjava.net/images/blogjava_net/dlevin/Reactor_Structures.png />


    Handle:即操作系统中的句柄,是对资源在操作系统层面上的一种抽象,它可以是打开的文件、一个连接(Socket)、Timer等。由于Reactor模式一般使用在网络编程中,因而这里一般指Socket Handle,即一个网络连接(Connection,在Java NIO中的Channel)。这个Channel注册到Synchronous Event Demultiplexer中,以监听Handle中发生的事件,对ServerSocketChannnel可以是CONNECT事件,对SocketChannel可以是READ、WRITE、CLOSE事件等。

    Synchronous Event Demultiplexer:阻塞等待一系列的Handle中的事件到来,如果阻塞等待返回,即表示在返回的Handle中可以不阻塞的执行返回的事件类型。这个模块一般使用操作系统的select来实现。在Java NIO中用Selector来封装,当Selector.select()返回时,可以调用Selector的selectedKeys()方法获取Set,一个SelectionKey表达一个有事件发生的Channel以及该Channel上的事件类型。上图的“Synchronous Event Demultiplexer —notifies–> Handle”的流程如果是对的,那内部实现应该是select()方法在事件到来后会先设置Handle的状态,然后返回。不了解内部实现机制,因而保留原图。

    Initiation Dispatcher:用于管理Event Handler,即EventHandler的容器,用以注册、移除EventHandler等;另外,它还作为Reactor模式的入口调用Synchronous Event Demultiplexer的select方法以阻塞等待事件返回,当阻塞等待返回时,根据事件发生的Handle将其分发给对应的Event Handler处理,即回调EventHandler中的handle_event()方法。

    Event Handler:定义事件处理方法:handle_event(),以供InitiationDispatcher回调使用。

    Concrete Event Handler:事件EventHandler接口,实现特定事件处理逻辑。


    Reactor模式模块之间的交互
    简单描述一下Reactor各个模块之间的交互流程,先从序列图开始:
    <img src=http://www.blogjava.net/images/blogjava_net/dlevin/Reactor_Sequence.png />

    1. 初始化InitiationDispatcher,并初始化一个Handle到EventHandler的Map。
    2. 注册EventHandler到InitiationDispatcher中,每个EventHandler包含对相应Handle的引用,从而建立Handle到EventHandler的映射(Map)。
    3. 调用InitiationDispatcher的handle_events()方法以启动Event Loop。在Event Loop中,调用select()方法(Synchronous Event Demultiplexer)阻塞等待Event发生。
    4. 当某个或某些Handle的Event发生后,select()方法返回,InitiationDispatcher根据返回的Handle找到注册的EventHandler,并回调该EventHandler的handle_events()方法。
    5. 在EventHandler的handle_events()方法中还可以向InitiationDispatcher中注册新的Eventhandler,比如对AcceptorEventHandler来,当有新的client连接时,它会产生新的EventHandler以处理新的连接,并注册到InitiationDispatcher中。

    Reactor模式实现
    在Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events中,一直以Logging Server来分析Reactor模式,这个Logging Server的实现完全遵循这里对Reactor描述,因而放在这里以做参考。Logging Server中的Reactor模式实现分两个部分:Client连接到Logging Server和Client向Logging Server写Log。因而对它的描述分成这两个步骤。


    Client连接到Logging Server
    <img src=http://www.blogjava.net/images/blogjava_net/dlevin/Reactor_LoggingServer_connect.png />

    1. Logging Server注册LoggingAcceptor到InitiationDispatcher。
    2. Logging Server调用InitiationDispatcher的handle_events()方法启动。
    3. InitiationDispatcher内部调用select()方法(Synchronous Event Demultiplexer),阻塞等待Client连接。
    4. Client连接到Logging Server。
    5. InitiationDisptcher中的select()方法返回,并通知LoggingAcceptor有新的连接到来。
    6. LoggingAcceptor调用accept方法accept这个新连接。
    7. LoggingAcceptor创建新的LoggingHandler。
    8. 新的LoggingHandler注册到InitiationDispatcher中(同时也注册到Synchonous Event Demultiplexer中),等待Client发起写log请求。

    Client向Logging Server写Log
    <img src=http://www.blogjava.net/images/blogjava_net/dlevin/Reactor_LoggingServer_log.png />

    1. Client发送log到Logging server。
    2. InitiationDispatcher监测到相应的Handle中有事件发生,返回阻塞等待,根据返回的Handle找到LoggingHandler,并回调LoggingHandler中的handle_event()方法。
    3. LoggingHandler中的handle_event()方法中读取Handle中的log信息。
    4. 将接收到的log写入到日志文件、数据库等设备中。
    3.4步骤循环直到当前日志处理完成。
    5. 返回到InitiationDispatcher等待下一次日志写请求。

    在Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events有对Reactor模式的C++的实现版本,多年不用C++,因而略过。


    Java NIO对Reactor的实现

    在Java的NIO中,对Reactor模式有无缝的支持,即使用Selector类封装了操作系统提供的Synchronous Event Demultiplexer功能。这个Doug Lea已经在Scalable IO In Java中有非常深入的解释了,因而不再赘述,另外<a href=http://www.cnblogs.com/luxiaoxun/archive/2015/03/11/4331110.html>这篇文章对Doug Lea的Scalable IO In Java有一些简单解释,至少它的代码格式比Doug Lea的PPT要整洁一些。


    需要指出的是,不同这里使用InitiationDispatcher来管理EventHandler,在Doug Lea的版本中使用SelectionKey中的Attachment来存储对应的EventHandler,因而不需要注册EventHandler这个步骤,或者设置Attachment就是这里的注册。而且在这篇文章中,Doug Lea从单线程的Reactor、Acceptor、Handler实现这个模式出发;演化为将Handler中的处理逻辑多线程化,实现类似Proactor模式,此时所有的IO操作还是单线程的,因而再演化出一个Main Reactor来处理CONNECT事件(Acceptor),而多个Sub Reactor来处理READ、WRITE等事件(Handler),这些Sub Reactor可以分别再自己的线程中执行,从而IO操作也多线程化。这个最后一个模型正是Netty中使用的模型。并且在Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events的9.5 Determine the Number of Initiation Dispatchers in an Application中也有相应的描述。

    EventHandler接口定义

    对EventHandler的定义有两种设计思路:single-method设计和multi-method设计:

    A single-method interface:它将Event封装成一个Event Object,EventHandler只定义一个handle_event(Event event)方法。这种设计的好处是有利于扩展,可以后来方便的添加新的Event类型,然而在子类的实现中,需要判断不同的Event类型而再次扩展成 不同的处理方法,从这个角度上来说,它又不利于扩展。另外在Netty3的使用过程中,由于它不停的创建ChannelEvent类,因而会引起GC的不稳定。

    A multi-method interface:这种设计是将不同的Event类型在 EventHandler中定义相应的方法。这种设计就是Netty4中使用的策略,其中一个目的是避免ChannelEvent创建引起的GC不稳定, 另外一个好处是它可以避免在EventHandler实现时判断不同的Event类型而有不同的实现,然而这种设计会给扩展新的Event类型时带来非常 大的麻烦,因为它需要该接口。

    关于Netty4对Netty3的改进可以参考这里:

    ChannelHandler with no event object
    In 3.x, every I/O operation created a ChannelEvent object. For each read / write, it additionally created a new ChannelBuffer. It simplified the internals of Netty quite a lot because it delegates resource management and buffer pooling to the JVM. However, it often was the root cause of GC pressure and uncertainty which are sometimes observed in a Netty-based application under high load.
    4.0 removes event object creation almost completely by replacing the event objects with strongly typed method invocations. 3.x had catch-all event handler methods such as handleUpstream() and handleDownstream(), but this is not the case anymore. Every event type has its own handler method now:

    为什么使用Reactor模式

    归功与Netty和Java NIO对Reactor的宣传,本文慕名而学习的Reactor模式,因而已经默认Reactor具有非常优秀的性能,然而慕名归慕名,到这里,我还是要不得不问自己Reactor模式的好处在哪里?即为什么要使用这个Reactor模式?

    在Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events中是这么说的:
    Reactor Pattern优点
    Separation of concerns: The Reactor pattern decouples application-independent demultiplexing and dispatching mechanisms from application-specific hook method functionality. The application-independent mechanisms become reusable components that know how to demultiplex events and dispatch the appropriate hook methods defined by Event Handlers. In contrast, the application-specific functionality in a hook method knows how to perform a particular type of service.

    Improve modularity, reusability, and configurability of event-driven applications: The pattern decouples application functionality into separate classes. For instance, there are two separate classes in the logging server: one for establishing connections and another for receiving and processing logging records. This decoupling enables the reuse of the connection establishment class for different types of connection-oriented services (such as file transfer, remote login, and video-on-demand). Therefore, modifying or extending the functionality of the logging server only affects the implementation of the logging handler class.

    Improves application portability: The Initiation Dispatcher’s interface can be reused independently of the OS system calls that perform event demultiplexing. These system calls detect and report the occurrence of one or more events that may occur simultaneously on multiple sources of events. Common sources of events may in- clude I/O handles, timers, and synchronization objects. On UNIX platforms, the event demultiplexing system calls are called select and poll [1]. In the Win32 API [16], the WaitForMultipleObjects system call performs event demultiplexing.

    Provides coarse-grained concurrency control: The Reactor pattern serializes the invocation of event handlers at the level of event demultiplexing and dispatching within a process or thread. Serialization at the Initiation Dispatcher level often eliminates the need for more complicated synchronization or locking within an application process.

    这些貌似是很多模式的共性:解耦、提升复用性、模块化、可移植性、事件驱动、细力度的并发控制等,因而并不能很好的说明什么,特别是它鼓吹的对性能的提升,这里并没有体现出来。

    当然在这篇文章的开头有描述过另一种直观的实现:Thread-Per-Connection,即传统的实现,提到了这个传统实现的以下问题:

    Thread Per Connection缺点

    Efficiency: Threading may lead to poor performance due to context switching, synchronization, and data movement [2];

    Programming simplicity: Threading may require complex concurrency control schemes;

    Portability: Threading is not available on all OS platforms.
    对于性能,它其实就是第一点关于Efficiency的描述,即线程的切换、同步、数据的移动会引起性能问题。也就是说从性能的角度上,它最大的提升就是减少了性能的使用,即不需要每个Client对应一个线程。我的理解,其他业务逻辑处理很多时候也会用到相同的线程,IO读写操作相对CPU的操作还是要慢很多,即使Reactor机制中每次读写已经能保证非阻塞读写,这里可以减少一些线程的使用,但是这减少的线程使用对性能有那么大的影响吗?答案貌似是肯定的,这篇论文(SEDA: Staged Event-Driven Architecture - An Architecture for Well-Conditioned, Scalable Internet Service)对随着线程的增长带来性能降低做了一个统计:

    在这个统计中,每个线程从磁盘中读8KB数据,每个线程读同一个文件,因而数据本身是缓存在操作系统内部的,即减少IO的影响;所有线程是事先分配的,不会有线程启动的影响;所有任务在测试内部产生,因而不会有网络的影响。该统计数据运行环境:Linux 2.2.14,2GB内存,4-way 500MHz Pentium III。从图中可以看出,随着线程的增长,吞吐量在线程数为8个左右的时候开始线性下降,并且到64个以后而迅速下降,其相应事件也在线程达到256个后指数上升。即1+1<2,因为线程切换、同步、数据移动会有性能损失,线程数增加到一定数量时,这种性能影响效果会更加明显。

    对于这点,还可以参考C10K Problem,用以描述同时有10K个Client发起连接的问题,到2010年的时候已经出现10M Problem了。

    当然也有人说:Threads are expensive are no longer valid.在不久的将来可能又会发生不同的变化,或者这个变化正在、已经发生着?没有做过比较仔细的测试,因而不敢随便断言什么,然而本人观点,即使线程变的影响并没有以前那么大,使用Reactor模式,甚至时SEDA模式来减少线程的使用,再加上其他解耦、模块化、提升复用性等优点,还是值得使用的。


    Reactor模式的缺点

    Reactor模式的缺点貌似也是显而易见的:

    1. 相比传统的简单模型,Reactor增加了一定的复杂性,因而有一定的门槛,并且不易于调试。
    2. Reactor模式需要底层的Synchronous Event Demultiplexer支持,比如Java中的Selector支持,操作系统的select系统调用支持,如果要自己实现Synchronous Event Demultiplexer可能不会有那么高效。
    3. Reactor模式在IO读写数据时还是在同一个线程中实现的,即使使用多个Reactor机制的情况下,那些共享一个Reactor的Channel如果出现一个长时间的数据读写,会影响这个Reactor中其他Channel的相应时间,比如在大文件传输时,IO操作就会影响其他Client的相应时间,因而对这种操作,使用传统的Thread-Per-Connection或许是一个更好的选择,或则此时使用Proactor模式。

    参考


    Reactor Pattern WikiPedia

    Reactor An Object Behavioral Pattern for Demultiplexing and Dispatching Handles for Synchronous Events

    Scalable IO In Java

    C10K Problem WikiPedia

    集合框架总结一

    Posted on 2019-08-18

    ##1. 集合的定义
    什么是集合呢?

    定义:集合是一个存放对象的引用的容器。
    在Java中,集合位于java.util包下。

    ##2. 集合和数组的区别(面试常问)
    提到容器,就会想起数组,那么集合和数组的区别是什么呢?(这里是重点,面试可能问的比较多)

  • 数组和集合都是Java中的容器

  • 数组的长度是固定的,集合的长度是可变的

  • 数组只能存储相同数据类型的数据,这里的数据类型可以是基本数据类型,也可以是引用类型

  • 集合可以存储不同数据类型的对象的引用(但一般情况下,我们会使用泛型来约定只使用1种数据类型),但不能存储基本数据类型


    空口无凭,我们来点代码配合理解,首先,我们看下如下的数组代码:


    1
    2
    3
    4
    5
    String[] platformArray = new String[3];
    platformArray[0] = "博客园";
    platformArray[1] = "掘金";
    platformArray[2] = "微信公众号";
    platformArray[3] = "个人博客";


    复制代码运行代码就会发现

    platformArray[3] = “个人博客”;
    会引发java.lang.ArrayIndexOutOfBoundsException异常。

    而使用集合时就不存在这个问题,因为集合在声明时不需要指定长度并且长度会根据放入元素的多少而变化:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    List<String> platformList = new ArrayList<>();
    platformList.add("博客园");
    platformList.add("掘金");
    platformList.add("微信公众号");
    platformList.add("个人博客");
    复制代码观察上面声明数组的代码,我们可以推断出下面的代码肯定是编译不通过的:
    String[] platformArray = new String[3];
    platformArray[0] = "博客园";
    platformArray[1] = "掘金";
    platformArray[2] = 1;
    复制代码因为数组声明时用的是String类型,而platformArray[2] = 1;赋值时却使用了int类型。
    再来看下下面的集合代码:
    List<int> intList = new ArrayList<int>();
    intList.add(1);
    intList.add(2);
    intList.add(3);
    复制代码这段代码也是编译不通过的,在IDEA中,鼠标悬停时会提示如下的错误信息:

    意思是类型参数不能是原始类型(基本数据类型),那怎么使用呢?总不能不让我使用int型的集合吧?
    当然不会,Java为每种基本数据类型都提供了对应的包装类,这里修改为int类型对应的包装类Integer即可:
    List<Integer> intList = new ArrayList<Integer>();
    intList.add(1);
    intList.add(2);
    intList.add(3);


    复制代码以下为Java的原始类型(基本数据类型)与其对应的包装类:



    |原始类型|包装类|
    | :——– | ——–: |
    |byte |Byte|
    |short|Short|
    |int|Integer|
    |long|Long|
    |float|Float|
    |double|Double|
    |char|Character|
    |boolean|Boolean|

    ##3. 集合的分类
    在Java中,集合主要分为以下3种:
  • List集合

  • Set集合

  • Map集合
  • 它们之间的继承关系可以参考下图。

    从上图可以总结出如下几点:

  • Java集合的根接口是Collection,它又继承了迭代接口Iterable

  • List接口和Set接口继承了Collection接口

  • Map接口是独立的接口,并没有继承Collection接口 (这里是重点,面试可能问的比较多)

  • List接口常用的实现类有:ArrayList、LinkedList、Vector

  • Set接口常用的实现类有:HashSet、LinkedHashSet、TreeSet

  • Map接口常用的实现类有:HashMap、HashTable、TreeMap
  • ##4. List集合
    List集合包括List接口以及List接口的所有实现类。List集合具有以下特点:

  • 集合中的元素允许重复
  • 集合中的元素是有顺序的,各元素插入的顺序就是各元素的顺序
  • 集合中的元素可以通过索引来访问或者设置
  • List接口常用的实现类有:ArrayList、LinkedList、Vector。
  • 我们先看下如下示例了解下List集合的用法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    package collection;

    import java.util.*;

    public class Muster {
    public static void main(String[] args) {
    List<String> strList = new ArrayList<>();
    strList.add("a");
    strList.add("b");
    strList.add("c");

    int i = (int) (Math.random() * strList.size());
    System.out.println("随机获取数组中的元素:" + strList.get(i));

    strList.remove(2);

    System.out.println("将索引为2的元素从列表移除后,数组中的元素是:");
    for (int j = 0; j < strList.size(); j++) {
    System.out.println(strList.get(j));
    }
    }
    }

    复制代码以上代码的输出结果为:


    随机获取数组中的元素:a
    将索引为2的元素从列表移除后,数组中的元素是:
    a
    b


    关于List集合的详细用法,ArrayList、LinkedList、Vector的区别(这里是重点,面试可能问的比较多),后续会单独写文总结,敬请期待。

    ##5. Set集合
    Set集合包括Set接口以及Set接口的所有实现类。Set集合具有以下特点:

  • 集合中不包含重复元素(你可以重复添加,但只会保留第1个)
  • 集合中的元素不一定保证有序
  • Set接口常用的实现类有:HashSet、LinkedHashSet、TreeSet。
  • 我们先看下如下示例了解下Set集合的用法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    package collection;

    import java.util.*;

    public class Muster {
    public static void main(String[] args) {
    Set<String> platformList = new HashSet<>();
    platformList.add("博客园");
    platformList.add("掘金");
    platformList.add("微信公众号");
    platformList.add("个人博客");

    // 尝试添加重复元素
    platformList.add("博客园");
    platformList.add("掘金");

    for (String platform : platformList) {
    System.out.println(platform);
    }
    }
    }

    复制代码以上代码的输出结果为:


    博客园
    个人博客
    掘金
    微信公众号

    可以看出,虽然我们尝试添加了重复元素,但并没有添加成功并且输出的元素没有顺序。
    因此当你的集合中不允许有重复元素并且对排序也没有要求的话,可以使用Set集合。
    关于Set集合的详细用法,HashSet、LinkedHashSet、TreeSet的区别(这里是重点,面试可能问的比较多),后续会单独写文总结,敬请期待。

    ##6. Map集合
    Map集合包括Map接口以及Map接口的所有实现类。
    Map集合具有以下特点:

  • Map接口并没有继承Collection接口,提供的是key到value的映射
  • Map中不能包含相同的key
  • Map接口常用的实现类有:HashMap、HashTable、TreeMap。
  • 我们先看下如下示例了解下Map集合的用法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    package collection;

    import java.util.*;

    public class Muster {
    public static void main(String[] args) {
    Map<Integer, String> platformMap = new HashMap<>();
    platformMap.put(1, "博客园");
    platformMap.put(2, "掘金");
    platformMap.put(3, "微信公众号");
    platformMap.put(4, "个人博客");

    // 尝试添加重复Map
    platformMap.put(4, "个人博客");

    // 获取所有的key
    Set<Integer> keys = platformMap.keySet();
    for (Integer integer : keys) {
    System.out.println("Key:" + integer + ",Value:" + platformMap.get(integer));
    }
    }
    }

    复制代码以上代码的输出结果为:

  • Key:1,Value:博客园
  • Key:2,Value:掘金
  • Key:3,Value:微信公众号
  • Key:4,Value:个人博客
  • 从日志可以看出,当我们尝试重加重复Map时,并没有添加成功。
  • 关于Map集合的详细用法,HashMap、HashTable、TreeMap的区别(这里是重点,面试可能问的比较多),后续会单独写文总结,敬请期待。
  • 一次生产问题的排查解决

    Posted on 2019-08-18

    导读
    java线上问题一直是个老大难的问题,紧急,信息量少,故障种类多,不易排查.那么在第一次遇到线上问题的时候我们可能会没有办法,但是随着解决问题的次数增多,对于很多线上问题,就会有了现象-分析-判定-解决这样的思路.开发人员在面对这样的问题时,一则是要见得多,从别人的博客中去分析提炼为什么会出现这样的,而不仅仅只满足答案. 二则是要能举一反三,
    理解了病症,病灶,病因,才能药到病除.



    • 病症

    • 病灶

    • 诊断

    • 分析

    • 对症下药


    病症


    典型的性能问题如页面响应慢、接口超时,服务器负载高、并发数低,数据库频繁死锁等。

    病灶


    Java 应用性能的瓶颈点非常多,比如磁盘、内存、网络 I/O 等系统因素,Java 应用代码,JVM GC,数据库,缓存等。
    Java 性能优化分为 4 个层级:应用层、数据库层、框架层、JVM 层

    诊断


    OS层面的诊断


    OS 的诊断主要关注的是 CPU、Memory、I/O 三个方面。

    对于 CPU 主要关注平均负载(Load Average),CPU 使用率,上下文切换次数(Context Switch)。

    通过 top 命令可以查看系统平均负载和 CPU 使用率

    通过 vmstat 命令可以查看 CPU 的上下文切换次数

    Memory

    从操作系统角度,内存关注应用进程是否足够,可以使用 free –m 命令查看内存的使用情况

    IO

    通过 iostat 可以查看磁盘的读写情况,通过 CPU 的 I/O wait 可以看出磁盘 I/O 是否正常。

    JVM层面的诊断

    jstack,JProfiler, jstat, jmap,MAT,

    对症下药


    GC 调优目标基本有三个思路:降低 GC 频率,可以通过增大堆空间,减少不必要对象生成;降低 GC 暂停时间,可以通过减少堆空间,使用 CMS GC 算法实现;避免 Full GC,调整 CMS 触发比例,避免 Promotion Failure 和 Concurrent mode failure(老年代分配更多空间,增加 GC 线程数加快回收速度),减少大对象生成等。

    业务逻辑层面的诊断

    并发业务,不要使用静态共享全局集合来操作Hashmap是线程不安全的,多线程put操作会导致存储的链表出现死循环

    1、业务日志相关

    如果系统出现异常或者业务有异常,首先想到的都是查看业务日志

    查看日志工具:

    less 或者more

    grep

    tail -f filename 查看实时的最新内容

    ps:切忌vim直接打开大日志文件,因为会直接加载到内存的

    2、数据库相关

    java应用很多瓶颈在数据库,一条sql没写好导致慢查询,可能就会带来应用带来致命危害。
    如果出现Could not get JDBC Connection 、接口响应慢、线程打满等,
    需要登录线上库,查看数据库连接情况:show processh6st,
    查看当前数据库的连接情况,确实由于慢查询造成,需要手动kill

    3、JVM相关

    java虚拟机相关的问题一般多是以下几种问题:gc时间过长、OOM、死锁、线程block、线程数暴涨等问题。一般通过以下几个工具都能定位出问题。

    jps命令

    作用
    是显示当前用户,当前系统的java进程情况,及其id号
    常用命令
    -m/-l/-v 查看运行参数

    jstat命令

    显示进程中的类装载、内存、垃圾收集、JIT编译等运行数据。
    常用指令
    jstat -gc 3331 250 20 :查询进程2764的垃圾收集情况,每250毫秒查询一次,一共查询20次。
    jstat -gccause:额外输出上次GC原因
    jstat -calss:件事类装载、类卸载、总空间以及所消耗的时间

    jstack命令

    功能
    生成当前时刻的线程快照。
    常用指令
    jstack 3331:查看线程情况
    jstack -F 3331:正常输出不被响应时,使用该指令
    jstack -l 3331:除堆栈外,显示关于锁的附件信息

    jmap命令

    功能
    生成堆转储快照(heapdump)
    常用指令
    jmap -heap 3331:查看java 堆(heap)使用情况
    jmap -histo 3331:查看堆内存(histogram)中的对象数量及大小
    jmap -histo:h6ve 3331:JVM会先触发gc,然后再统计信息
    jmap -dump:format=b,file=heapDump 3331:将内存使用的详细情况输出到文件,之后一般使用其他工具进行分析。

    jhat命令 略

    3.1 OOM问题或者频繁GC问题

    发生OOM问题一般服务都会crash,业务日志会有OutOfMemoryError。OOM一般都是出现了内存泄露,需要查看OOM时候的jvm堆的快照,如果配置了-XX:+HeapDumpOnOutOfMemoryError, 在发生OOM的时候会在-XX:HeapDumpPath生成堆的dump文件,结合MAT,可以对dump文件进行分析,查找出发生OOM的原因. 关于MAT使用不详述了,google上一堆(http://inter12.iteye.com/blog/1407492)。
    ps.
    1、服务器的内存一般较大,所以要保证服务器的磁盘空间大于内存大小
    2、另外手动dump堆快照,可以使用命令jmap -dump:format=b,file=file_name pid

    3.2 死锁

    死锁原因是两个或者多个线程相互等待资源,现象一般是出现线程hung住,更严重会出现线程数暴涨,系统出现api ah6ve报警等。
    查看死锁最好的方法就是分析当时的线程栈。
    具体case 可以参考jstack命令里面的例子
    用到的命令:
    jps -v
    jstack -l pid

    3.3 线程block、线程数暴涨

    jstack -l pid |wc -l
    jstack -l pid |grep “BLOCKED”|wc -l
    jstack -l pid |grep “Waiting on condition”|wc -l

    线程block问题一般是等待io、等待网络、等待监视器锁等造成,可能会导致请求超时、造成造成线程数暴涨导致系统502等。

    如果出现这种问题,主要是关注jstack 出来的BLOCKED、Waiting on condition、Waiting on monitor entry等状态信息。

    如果大量线程在“waiting for monitor entry”:
    可能是一个全局锁阻塞住了大量线程。

    如果短时间内打印的 thread dump 文件反映,随着时间流逝,waiting for monitor entry 的线程越来越多,没有减少的趋势,可能意味着某些线程在临界区里呆的时间太长了,以至于越来越多新线程迟迟无法进入临界区。

    如果大量线程在“waiting on condition”:
    可能是它们又跑去获取第三方资源,迟迟获取不到Response,导致大量线程进入等待状态。
    所以如果你发现有大量的线程都处在 Wait on condition,从线程堆栈看,正等待网络读写,这可能是一个网络瓶颈的征兆,因为网络阻塞导致线程无法执行。

    3.4 gc时间过长

    先贴一个文章占坑:<a href= http://www.oracle.com/technetwork/cn/articles/java/g1gc-1984535-zhs.html>传送门

    4、服务器问题

    4.1 CPU

    top命令(参考https://h6nux.cn/article-2352-1.html)
    主要关注cpu的load,以及比较耗cpu的进程
    由于现在服务器都是虚拟机,还要关注st(st 的全称是 Steal Time ,是分配给运行在其它虚拟机上的任务的实际 CPU 时间)
    常用交互命令:
    h 帮助,十分有用
    R: 反向排序
    x:将排序字段高亮显示(纵列)
    y 将运行进程高亮显示(横行)
    shift+> 或shift+<:切换排序字段
    d或s: 设置显示的刷新间隔
    f: 字段管理 设置显示的字段
    k:kill进程

    4.2 内存

    free命令:
    free -m -c10 -s1
    -m:以MB为单位显示,其他的有-k -g -b
    -s: 间隔多少秒持续观察内存使用状况
    -c:观察多少次
    vmstat命令:(http://man.h6nuxde.net/vmstat)
    vmstat 1 10
    1表示每隔1s输出一次,10 表示输出10次
    两个参数需要关注
    r: 运行队列中进程数量,这个值也可以判断是否需要增加CPU。(长期大于1)
    b: 等待IO的进程数量。

    4.3 IO

    iostat 命令(http://www.orczhou.com/index.php/2010/03/iostat-detail/)
    iostat -m 1 10
    -m:某些使用block为单位的列强制使用MB为单位
    1 10:数据显示每隔1秒刷新一次,共显示10次

    4.4 网络

    netstat 命令(http://www.cnblogs.com/ggjucheng/archive/2012/01/08/2316661.html)
    netstat -antp

    -a (all)显示所有选项,默认不显示h6STEN相关
    -t (tcp)仅显示tcp相关选项
    -u (udp)仅显示udp相关选项
    -n 拒绝显示别名,能显示数字的全部转化成数字。
    -l 仅列出有在 h6sten (监听) 的服服务状态
    -p 显示建立相关链接的程序名

    显示tcp各个状态数量:
    netstat -ant |awk ‘{print $6}’|sort|uniq -c

    查看连接某服务端口最多的的IP地址
    netstat -nat | grep “10.32.45.35:8924” |awk ‘{print $5}’|awk -F: ‘{print $4}’|sort|uniq -c|sort -nr|head -10

    JUC系列-线程池原理

    Posted on 2019-03-20

    ThreadPoolExecutor使用介绍

    
    private static ExecutorService exec = new ThreadPoolExecutor(8, 8, 0L,
    TimeUnit.MILLISECONDS, new LinkedBlockingQueue(100000),
    new ThreadPoolExecutor.CallerRunsPolicy());
    

    一、简介



    线程池类为 java.util.concurrent.ThreadPoolExecutor,常用构造方法为:
    ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,
    long keepAliveTime, TimeUnit unit,
    BlockingQueue workQueue,
    RejectedExecutionHandler handler)

    corePoolSize: 线程池维护线程的最少数量
    maximumPoolSize:线程池维护线程的最大数量
    keepAliveTime: 线程池维护线程所允许的空闲时间
    unit: 线程池维护线程所允许的空闲时间的单位
    workQueue: 线程池所使用的缓冲队列
    handler: 线程池对拒绝任务的处理策略



    一个任务通过 execute(Runnable)方法被添加到线程池,任务就是一个 Runnable类型的对象,任务的执行方法就是Runnable类型对象的run()方法。


    二、原理


    当一个任务通过execute(Runnable)方法欲添加到线程池时:
    1. 如果此时线程池中的数量小于corePoolSize,即使线程池中的线程都处于空闲状态,也要创建新的线程来处理被添加的任务。
    2. 如果此时线程池中的数量等于 corePoolSize,但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。
    3. 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新的线程来处理被添加的任务。
    4. 如果此时线程池中的数量大于corePoolSize,缓冲队列workQueue满,并且线程池中的数量等于maximumPoolSize,那么通过 handler所指定的策略来处理此任务。也就是:处理任务的优先级为:核心线程corePoolSize、任务队列workQueue、最大线程maximumPoolSize,如果三者都满了,使用handler处理被拒绝的任务。
    5. 当线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止。这样,线程池可以动态的调整池中的线程数。
    6. unit可选的参数为java.util.concurrent.TimeUnit中的几个静态属性:
    NANOSECONDS、
    MICROSECONDS、
    MILLISECONDS、
    SECONDS。

    7. workQueue常用的是:java.util.concurrent.ArrayBlockingQueue
    8. handler有四个选择:

    1. ThreadPoolExecutor.AbortPolicy()
    抛出java.util.concurrent.RejectedExecutionException异常

    2. ThreadPoolExecutor.CallerRunsPolicy()
    当抛出RejectedExecutionException异常时,会调用rejectedExecution方法
    (如果主线程没有关闭,则主线程调用run方法,源码如下:
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    if (!e.isShutdown()) {
    r.run();
    }
    }

    )

    3. ThreadPoolExecutor.DiscardOldestPolicy()
    抛弃旧的任务
    4. ThreadPoolExecutor.DiscardPolicy()
    抛弃当前的任务


    三、相关参考


    一个 ExecutorService,它使用可能的几个池线程之一执行每个提交的任务,通常使用 Executors 工厂方法配置。

    线程池可以解决两个不同问题:由于减少了每个任务调用的开销,它们通常可以在执行大量异步任务时提供增强的性能,并且还可以提供绑定和管理资源(包括执行集合任务时使用的线程)的方法。每个ThreadPoolExecutor 还维护着一些基本的统计数据,如完成的任务数。

    为了便于跨大量上下文使用,此类提供了很多可调整的参数和扩展挂钩。但是,强烈建议程序员使用较为方便的 Executors 工厂方法 Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收)、Executors.newFixedThreadPool(int)(固定大小线程池)和 Executors.newSingleThreadExecutor()(单个后台线程),它们均为大多数使用场景预定义了设置。否则,在手动配置和调整此类时,使用以下指导:

    核心和最大池大小


    ThreadPoolExecutor 将根据 corePoolSize(参见 getCorePoolSize())和 maximumPoolSize(参见getMaximumPoolSize())设置的边界自动调整池大小。
    当新任务在方法 execute(java.lang.Runnable) 中提交时,如果运行的线程少于 corePoolSize,则创建新线程来处理请求,即使其他辅助线程是空闲的。

    如果运行的线程多于corePoolSize 而少于 maximumPoolSize,则仅当队列满时才创建新线程。如果设置的 corePoolSize 和 maximumPoolSize相同,则创建了固定大小的线程池。
    如果将 maximumPoolSize 设置为基本的无界值(如 Integer.MAX_VALUE),则允许池适应任意数量的并发任务。在大多数情况下,核心和最大池大小仅基于构造来设置,不过也可以使用setCorePoolSize(int) 和 setMaximumPoolSize(int) 进行动态更改。



    按需构造


    默认情况下,即使核心线程最初只是在新任务需要时才创建和启动的,也可以使用方法 prestartCoreThread()或 prestartAllCoreThreads() 对其进行动态重写。

    创建新线程


    使用 ThreadFactory 创建新线程。如果没有另外说明,则在同一个 ThreadGroup 中一律使用Executors.defaultThreadFactory() 创建线程,并且这些线程具有相同的 NORM_PRIORITY 优先级和非守护进程状态。通过提供不同的 ThreadFactory,可以改变线程的名称、线程组、优先级、守护进程状态,等等。如果从 newThread返回 null 时 ThreadFactory 未能创建线程,则执行程序将继续运行,但不能执行任何任务。

    保持活动时间


    如果池中当前有多于 corePoolSize 的线程,则这些多出的线程在空闲时间超过 keepAliveTime 时将会终止(参见getKeepAliveTime(java.util.concurrent.TimeUnit))。这提供了当池处于非活动状态时减少资源消耗的方法。如果池后来变得更为活动,则可以创建新的线程。也可以使用方法 setKeepAliveTime(long, java.util.concurrent.TimeUnit) 动态地更改此参数。使用 Long.MAX_VALUE TimeUnit.NANOSECONDS 的值在关闭前有效地从以前的终止状态禁用空闲线程。

    排队


    所有 BlockingQueue 都可用于传输和保持提交的任务。可以使用此队列与池大小进行交互:
    1. 如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。
    2. 如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。
    3.如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。

    排队有三种通用策略:


    1. 直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集合时出现锁定。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。

    2. 无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙的情况下将新任务加入队列。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。

    3. 有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。

    被拒绝的任务


    当 Executor 已经关闭,并且 Executor 将有限边界用于最大线程和工作队列容量,且已经饱和时,在方法execute(java.lang.Runnable) 中提交的新任务将被拒绝。在以上两种情况下,execute 方法都将调用其RejectedExecutionHandler 的 RejectedExecutionHandler.rejectedExecution(java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor) 方法。
    下面提供了四种预定义的处理程序策略:
    1.在默认的 ThreadPoolExecutor.AbortPolicy 中,处理程序遭到拒绝将抛出运行时 RejectedExecutionException。
    2.在 ThreadPoolExecutor.CallerRunsPolicy 中,线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。
    3.在 ThreadPoolExecutor.DiscardPolicy 中,不能执行的任务将被删除。
    4.在 ThreadPoolExecutor.DiscardOldestPolicy 中,如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)。
    5. 定义和使用其他种类的 RejectedExecutionHandler 类也是可能的,但这样做需要非常小心,尤其是当策略仅用于特定容量或排队策略时。

    挂钩方法


    此类提供 protected 可重写的 beforeExecute(java.lang.Thread, java.lang.Runnable) 和 afterExecute(java.lang.Runnable, java.lang.Throwable) 方法,这两种方法分别在执行每个任务之前和之后调用。它们可用于操纵执行环境;例如,重新初始化ThreadLocal、搜集统计信息或添加日志条目。此外,还可以重写方法 terminated() 来执行 Executor 完全终止后需要完成的所有特殊处理。


    如果挂钩或回调方法抛出异常,则内部辅助线程将依次失败并突然终止。


    队列维护


    方法 getQueue() 允许出于监控和调试目的而访问工作队列。强烈反对出于其他任何目的而使用此方法。remove(java.lang.Runnable) 和 purge() 这两种方法可用于在取消大量已排队任务时帮助进行存储回收。


    一、例子
    创建 TestThreadPool 类




    import java.util.concurrent.ArrayBlockingQueue;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;

    public class TestThreadPool {

    private static int produceTaskSleepTime = 2;

    private static int produceTaskMaxNumber = 10;

    public static void main(String[] args) {

    // 构造一个线程池
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 3,
    TimeUnit.SECONDS, new ArrayBlockingQueue(3),
    new ThreadPoolExecutor.DiscardOldestPolicy());

    for (int i = 1; i <= produceTaskMaxNumber; i++) {
    try {
    String task = “task@ “ + i;
    System.out.println(“创建任务并提交到线程池中:” + task);
    threadPool.execute(new ThreadPoolTask(task));

    Thread.sleep(produceTaskSleepTime);
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    }
    }

    import java.io.Serializable;

    public class ThreadPoolTask implements Runnable, Serializable {

    private Object attachData;

    ThreadPoolTask(Object tasks) {
    this.attachData = tasks;
    }

    public void run() {

    System.out.println(“开始执行任务:” + attachData);

    attachData = null;
    }

    public Object getTask() {
    return this.attachData;
    }
    }



    执行结果:

    创建任务并提交到线程池中:task@ 1

    开始执行任务:task@ 1

    创建任务并提交到线程池中:task@ 2

    开始执行任务:task@ 2

    创建任务并提交到线程池中:task@ 3

    创建任务并提交到线程池中:task@ 4

    开始执行任务:task@ 3

    创建任务并提交到线程池中:task@ 5

    开始执行任务:task@ 4

    创建任务并提交到线程池中:task@ 6

    创建任务并提交到线程池中:task@ 7

    创建任务并提交到线程池中:task@ 8

    开始执行任务:task@ 5

    开始执行任务:task@ 6

    创建任务并提交到线程池中:task@ 9

    开始执行任务:task@ 7

    创建任务并提交到线程池中:task@ 10

    开始执行任务:task@ 8

    开始执行任务:task@ 9

    开始执行任务:task@ 10




    四.ThreadPoolExecutor配置

    1. ThreadPoolExcutor为一些Executor提供了基本的实现,这些Executor是由Executors中的工厂 newCahceThreadPool、newFixedThreadPool和newScheduledThreadExecutor返回的。 ThreadPoolExecutor是一个灵活的健壮的池实现,允许各种各样的用户定制。

    2. 线程的创建与销毁

      1. 核心池大小、最大池大小和存活时间共同管理着线程的创建与销毁。
      2. 核心池的大小是目标的大小;线程池的实现试图维护池的大小;即使没有任务执行,池的大小也等于核心池的大小,并直到工作队列充满前,池都不会创建更多的线程。如果当前池的大小超过了核心池的大小,线程池就会终止它。
    3. 最大池的大小是可同时活动的线程数的上限。

    4. 如果一个线程已经闲置的时间超过了存活时间,它将成为一个被回收的候选者。
    5. newFixedThreadPool工厂为请求的池设置了核心池的大小和最大池的大小,而且池永远不会超时
    6. newCacheThreadPool工厂将最大池的大小设置为Integer.MAX_VALUE,核心池的大小设置为0,超时设置为一分钟。这样创建了无限扩大的线程池,会在需求量减少的情况下减少线程数量。

    五.管理

    1. ThreadPoolExecutor允许你提供一个BlockingQueue来持有等待执行的任务。任务排队有3种基本方法:无限队列、有限队列和同步移交。
    2. newFixedThreadPool和newSingleThreadExectuor默认使用的是一个无限的 LinkedBlockingQueue。如果所有的工作者线程都处于忙碌状态,任务会在队列中等候。如果任务持续快速到达,超过了它们被执行的速度,队列也会无限制地增加。稳妥的策略是使用有限队列,比如ArrayBlockingQueue或有限的LinkedBlockingQueue以及 PriorityBlockingQueue。
    3. 对于庞大或无限的池,可以使用SynchronousQueue,完全绕开队列,直接将任务由生产者交给工作者线程
    4. 可以使用PriorityBlockingQueue通过优先级安排任务

    JUC系列-Java实现生产者消费者模型

    Posted on 2019-03-10

    JUC系列-Java实现生产者-消费者模型


    有如下考点:

    1.对java并发模型的理解

    2.对并发编程接口的熟练程度

    jdk: oracle java 1.8.0_102

    本文主要归纳了3种写法,阅读后,最好在白板上练习几遍,检查自己是否掌握。这4种写法或者编程接口不同,或者并发粒度不同,但本质是相同的——都是在使用或实现BlockingQueue。

    生产者-消费者模型


    网上有很多生产者-消费者模型的定义和实现。本文研究最常用的有界生产者-消费者模型,简单概括如下:

    1. 生产者持续生产,直到缓冲区满,阻塞;缓冲区不满后,继续生产

    2. 消费者持续消费,直到缓冲区空,阻塞;缓冲区不空后,继续消费

    3. 生产者可以有多个,消费者也可以有多个


    4. 可通过如下条件验证模型实现的正确性:
    5. 同一产品的消费行为一定发生在生产行为之后

    6. 任意时刻,缓冲区大小不小于0,不大于限制容量

    7. 该模型的应用和变种非常多,不赘述。



    准备


    关键部分需要实现,抽象,如AbstractConsumer。


    下面会涉及多种生产者-消费者模型的实现,可以先抽象出关键的接口,并实现一些抽象类


    public interface Consumer {
    void consume() throws InterruptedException;
    }


    public interface Producer {
    void produce() throws InterruptedException;
    }

    abstract class AbstractConsumer implements Consumer, Runnable {
    @Override
    public void run() {
    while (true) {
    try {
    consume();
    } catch (InterruptedException e) {
    e.printStackTrace();
    break;
    }
    }
    }
    }

    abstract class AbstractProducer implements Producer, Runnable {
    @Override
    public void run() {
    while (true) {
    try {
    produce();
    } catch (InterruptedException e) {
    e.printStackTrace();
    break;
    }
    }
    }
    }



    不同的模型实现中,生产者、消费者的具体实现也不同,所以需要为模型定义抽象工厂方法:



    public interface Model {
    Runnable newRunnableConsumer();
    Runnable newRunnableProducer();
    }

    我们将Task作为生产和消费的单位
    public class Task {
    public int no;
    public Task(int no) {
    this.no = no;
    }
    }



    如果需求还不明确(这符合大部分工程工作的实际情况),建议边实现边抽象,不要“面向未来编程”。

    实现一:BlockingQueue

    BlockingQueue的写法最简单。核心思想是,把并发和容量控制封装在缓冲区中。而BlockingQueue的性质天生满足这个要求。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65

    public class BlockingQueueModel implements Model {
    private final BlockingQueue<Task> queue;
    private final AtomicInteger increTaskNo = new AtomicInteger(0);
    public BlockingQueueModel(int cap) {
    // LinkedBlockingQueue 的队列不 init,入队时检查容量;ArrayBlockingQueue 在创建时 init
    this.queue = new LinkedBlockingQueue<>(cap);
    }
    @Override
    public Runnable newRunnableConsumer() {
    return new ConsumerImpl();
    }
    @Override
    public Runnable newRunnableProducer() {
    return new ProducerImpl();
    }
    private class ConsumerImpl extends AbstractConsumer implements Consumer, Runnable {
    @Override
    public void consume() throws InterruptedException {
    Task task = queue.take();
    // 固定时间范围的消费,模拟相对稳定的服务器处理过程
    Thread.sleep(500 + (long) (Math.random() * 500));
    System.out.println("consume: " + task.no);
    }
    }
    private class ProducerImpl extends AbstractProducer implements Producer, Runnable {
    @Override
    public void produce() throws InterruptedException {
    // 不定期生产,模拟随机的用户请求
    Thread.sleep((long) (Math.random() * 1000));
    Task task = new Task(increTaskNo.getAndIncrement());
    System.out.println("produce: " + task.no);
    queue.put(task);
    }
    }
    public static void main(String[] args) {
    Model model = new BlockingQueueModel(3);
    for (int i = 0; i < 2; i++) {
    new Thread(model.newRunnableConsumer()).start();
    }
    for (int i = 0; i < 5; i++) {
    new Thread(model.newRunnableProducer()).start();
    }
    }
    }

    截取前面的一部分输出:
    produce: 0
    produce: 4
    produce: 2
    produce: 3
    produce: 5
    consume: 0
    produce: 1
    consume: 4
    produce: 7
    consume: 2
    produce: 8
    consume: 3
    produce: 6
    consume: 5
    produce: 9
    consume: 1
    produce: 10
    consume: 7

    验证条件


    由于操作“出队/入队+日志输出”不是原子的,所以上述日志的绝对顺序与实际的出队/入队顺序有出入,但对于同一个任务号task.no,其consume日志一定出现在其produce日志之后,即:同一任务的消费行为一定发生在生产行为之后。缓冲区的容量留给读者验证。符合两个验证条件。

    BlockingQueue写法的核心只有两行代码,并发和容量控制都封装在了BlockingQueue中,正确性由BlockingQueue保证。

    实现二:wait && notify


    如果不能将并发与容量控制都封装在缓冲区中,就只能由消费者与生产者完成。最简单的方案是使用朴素的wait && notify机制。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    public class WaitNotifyModel implements Model {
    private final Object BUFFER_LOCK = new Object();
    private final Queue<Task> buffer = new LinkedList<>();
    private final int cap;
    private final AtomicInteger increTaskNo = new AtomicInteger(0);
    public WaitNotifyModel(int cap) {
    this.cap = cap;
    }
    @Override
    public Runnable newRunnableConsumer() {
    return new ConsumerImpl();
    }
    @Override
    public Runnable newRunnableProducer() {
    return new ProducerImpl();
    }
    private class ConsumerImpl extends AbstractConsumer implements Consumer, Runnable {
    @Override
    public void consume() throws InterruptedException {
    synchronized (BUFFER_LOCK) {
    while (buffer.size() == 0) {
    BUFFER_LOCK.wait();
    }
    Task task = buffer.poll();
    assert task != null;
    // 固定时间范围的消费,模拟相对稳定的服务器处理过程
    Thread.sleep(500 + (long) (Math.random() * 500));
    System.out.println("consume: " + task.no);
    BUFFER_LOCK.notifyAll();
    }
    }
    }
    private class ProducerImpl extends AbstractProducer implements Producer, Runnable {
    @Override
    public void produce() throws InterruptedException {
    // 不定期生产,模拟随机的用户请求
    Thread.sleep((long) (Math.random() * 1000));
    synchronized (BUFFER_LOCK) {
    while (buffer.size() == cap) {
    BUFFER_LOCK.wait();
    }
    Task task = new Task(increTaskNo.getAndIncrement());
    buffer.offer(task);
    System.out.println("produce: " + task.no);
    BUFFER_LOCK.notifyAll();
    }
    }
    }
    public static void main(String[] args) {
    Model model = new WaitNotifyModel(3);
    for (int i = 0; i < 2; i++) {
    new Thread(model.newRunnableConsumer()).start();
    }
    for (int i = 0; i < 5; i++) {
    new Thread(model.newRunnableProducer()).start();
    }
    }
    }

    实现三:简单的Lock && Condition


    我们要保证理解wait && notify机制。实现时可以使用Object类提供的wait()方法与notifyAll()方法,但更推荐的方式是使用java.util.concurrent包提供的Lock && Condition。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66

    public class LockConditionModel1 implements Model {
    private final Lock BUFFER_LOCK = new ReentrantLock();
    private final Condition BUFFER_COND = BUFFER_LOCK.newCondition();
    private final Queue<Task> buffer = new LinkedList<>();
    private final int cap;
    private final AtomicInteger increTaskNo = new AtomicInteger(0);
    public LockConditionModel1(int cap) {
    this.cap = cap;
    }
    @Override
    public Runnable newRunnableConsumer() {
    return new ConsumerImpl();
    }
    @Override
    public Runnable newRunnableProducer() {
    return new ProducerImpl();
    }
    private class ConsumerImpl extends AbstractConsumer implements Consumer, Runnable {
    @Override
    public void consume() throws InterruptedException {
    BUFFER_LOCK.lockInterruptibly();
    try {
    while (buffer.size() == 0) {
    BUFFER_COND.await();
    }
    Task task = buffer.poll();
    assert task != null;
    // 固定时间范围的消费,模拟相对稳定的服务器处理过程
    Thread.sleep(500 + (long) (Math.random() * 500));
    System.out.println("consume: " + task.no);
    BUFFER_COND.signalAll();
    } finally {
    BUFFER_LOCK.unlock();
    }
    }
    }
    private class ProducerImpl extends AbstractProducer implements Producer, Runnable {
    @Override
    public void produce() throws InterruptedException {
    // 不定期生产,模拟随机的用户请求
    Thread.sleep((long) (Math.random() * 1000));
    BUFFER_LOCK.lockInterruptibly();
    try {
    while (buffer.size() == cap) {
    BUFFER_COND.await();
    }
    Task task = new Task(increTaskNo.getAndIncrement());
    buffer.offer(task);
    System.out.println("produce: " + task.no);
    BUFFER_COND.signalAll();
    } finally {
    BUFFER_LOCK.unlock();
    }
    }
    }
    public static void main(String[] args) {
    Model model = new LockConditionModel1(3);
    for (int i = 0; i < 2; i++) {
    new Thread(model.newRunnableConsumer()).start();
    }
    for (int i = 0; i < 5; i++) {
    new Thread(model.newRunnableProducer()).start();
    }
    }
    }

    该写法的思路与实现二的思路完全相同,仅仅将锁与条件变量换成了Lock和Condition。

    总结


    方法1最简单,线程的并发同步和容量控制全部交给了LinkedBlockingQueue实现,自然美观,封装了复杂度.

    方法2和方法3是相同的原理,使用了手动控制并发同步,锁和竞争条件的运用.这对于我深入到阻塞队列实现的阻塞原理有了更加客观的认识.

    如果还有其他的实现方式,欢迎补充探讨,再次感谢阅读并指正.


    author:shengfq
    qq:1085748383
    email: 1085748383@qq.com
    date:2019-03-10
    

    JUC多线程系列-ArrayBlockingQueue源码分析

    Posted on 2018-11-19

    JUC多线程系列-ArrayBlockingQueue源码分析


    背景


    最近在项目中应用到后台异步任务并发应用,干脆系统的撸一遍JUC,然后应用到项目中实战.

    使用阻塞队列实现生产者消费者模式


    优点:阻塞队列实现生产者消费者模式超级简单,它提供开箱即用支持阻塞的方法put()和take(),开发者不需要写困惑的wait-nofity代码去实现通信。BlockingQueue 一个接口,Java5提供了不同的现实,如ArrayBlockingQueue和LinkedBlockingQueue,两者都是先进先出(FIFO)顺序。而ArrayLinkedQueue是自然有界的,LinkedBlockingQueue可选的边界。下面这是一个完整的生产者消费者代码例子,对比传统的wait、nofity代码,它更易于理解。


    目录



    1. ArrayBlockingQueue介绍

    2. ArrayBlockingQueue原理和数据结构

    3. ArrayBlockingQueue函数列表

    4. ArrayBlockingQueue源码分析(JDK1.8版本)

    5. ArrayBlockingQueue示例



    正文


    1.ArrayBlockingQueue介绍


    ArrayBlockingQueue是数组实现的线程安全的有界的阻塞队列。
    线程安全是指,ArrayBlockingQueue内部通过“互斥锁”保护竞争资源,实现了多线程对竞争资源的互斥访问。而有界,则是指ArrayBlockingQueue对应的数组是有界限的。 阻塞队列,是指多线程访问竞争资源时,当竞争资源已被某线程获取时,其它要获取该资源的线程需要阻塞等待;而且,ArrayBlockingQueue是按 FIFO(先进先出)原则对元素进行排序,元素都是从尾部插入到队列,从头部开始返回。



    注意:ArrayBlockingQueue不同于ConcurrentLinkedQueue,ArrayBlockingQueue是数组实现的,并且是有界限的;而ConcurrentLinkedQueue是链表实现的,是无界限的。


    2.ArrayBlockingQueue原理和数据结构



    1. ArrayBlockingQueue继承于AbstractQueue,并且它实现了BlockingQueue接口。

    2. ArrayBlockingQueue内部是通过Object[]数组保存数据的,也就是说ArrayBlockingQueue本质上是通过数组实现的。ArrayBlockingQueue的大小,即数组的容量是创建ArrayBlockingQueue时指定的。

    3. ArrayBlockingQueue与ReentrantLock是组合关系,ArrayBlockingQueue中包含一个ReentrantLock对象(lock)。ReentrantLock是可重入的互斥锁,ArrayBlockingQueue就是根据该互斥锁实现“多线程对竞争资源的互斥访问”。而且,ReentrantLock分为公平锁和非公平锁,关于具体使用公平锁还是非公平锁,在创建ArrayBlockingQueue时可以指定;而且,ArrayBlockingQueue默认会使用非公平锁。

    4. ArrayBlockingQueue与Condition是组合关系,ArrayBlockingQueue中包含两个Condition对象(notEmpty和notFull)。



    3.ArrayBlockingQueue函数列表

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41

    private ArrayBlockingQueue<Integer> queue=new ArrayBlockingQueue<Integer>(size);
    // 将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true,如果此队列已满,则抛出 IllegalStateException。
    private boolean add(Integer num){
    return queue.add(num);
    }
    // 将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true,如果此队列已满,则返回 false。
    private boolean offer(Integer num){
    return queue.offer(num);
    }
    / 创建一个带有给定的(固定)容量和默认访问策略的 ArrayBlockingQueue。
    ArrayBlockingQueue(int capacity)
    // 创建一个具有给定的(固定)容量和指定访问策略的 ArrayBlockingQueue。
    ArrayBlockingQueue(int capacity, boolean fair)
    // 创建一个具有给定的(固定)容量和指定访问策略的 ArrayBlockingQueue,它最初包含给定 collection 的元素,并以 collection 迭代器的遍历顺序添加元素。
    ArrayBlockingQueue(int capacity, boolean fair, Collection<? extends E> c)

    //TODO 将指定的元素插入此队列的尾部,如果该队列已满,则等待可用的空间。
    private void put(Integer num) throws InterruptedException {
    queue.put(num);
    }
    // 获取但不移除此队列的头;如果此队列为空,则返回 null。
    private Integer peek(){
    return queue.peek();
    }
    // 获取并移除此队列的头,如果此队列为空,则返回 null。
    private Integer poll(){
    return queue.poll();
    }
    //TODO 获取并移除此队列的头部,在元素变得可用之前一直等待(如果有必要)。
    private Integer take() throws InterruptedException{
    return queue.take();
    }
    // 从此队列中移除指定元素的单个实例(如果存在)
    private boolean remove(Integer num){
    return queue.remove(num);
    }
    // 返回此队列中元素的数量。
    private int getSize(){
    return queue.size();
    }

    4.源码分析

    创建

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    public ArrayBlockingQueue(int capacity, boolean fair) {
    if (capacity <= 0)
    throw new IllegalArgumentException();
    this.items = new Object[capacity];//存放数组
    lock = new ReentrantLock(fair); //互斥锁
    notEmpty = lock.newCondition();//竞争条件
    notFull = lock.newCondition();//竞争条件
    }

    添加
    offer() //队列满了返回false
    add() //队列满了抛出异常
    put() //队列慢了,阻塞线程
    取出
    poll()//获取并移除此队列的头,如果此队列为空,则返回 null。
    remove()//获取并移除此队列的头,如果为空,返回false
    take() //获取并移除此队列的头部,在元素变得可用之前一直等待(如果有必要)。
    遍历
    iterator() //符合Collection 接口

    总结


    这些API都不需要记忆,因为编程的时候,你可以查看API的实现原理.重点是要花时间把他看懂,知道为什么实现的.

    5.思考题ArrayBlockingQueue示例
    一个线程打印1-52,一个线程打印A-Z
    要求控制台输出A12B34C56….Z5152

    遗留系统的异常处理办法

    Posted on 2018-09-23

    遗留系统的异常处理办法

    背景

    OA7.0是一个跨度较大的版本,拷贝了客户生产环境的webapp/resources/data 就可以在本地搭建其测试环境。
    根据客户的二开策略,我修改了数据源等个性化配置信息后遇到了些问题:
    1.启动报各种异常导致无法启动成功
    2.启动成功后,使用用户名和密码都无法登录成功
    在这种情形下,就要进入具体的报错点,熟悉类的加载过程。

    java.lang.NoClassDefFoundError: Could not initialize class org.springside.core.Constants
    
    Oracle 的解释是:
    Thrown if the Java Virtual Machine or a ClassLoader instance tries to load in the definition of a class (as part of a normal method call or as part of creating a new instance using the new expression) and no definition of the class could be found.
    The searched-for class definition existed when the currently executing class was compiled, but the definition can no longer be found.
    
    当java虚拟机或者类加载器实例尝试加载定义的类时,却没有找到该类的定义。
    

    解决思路

    注释法


    类会先加载static块,采用二分法的方式注释一部分方法调用,看问题是否存在。

    日志法


    生产环境的问题无法debug,但是可以通过详细的日志调用,知道执行了哪些问题。

    反编译法


    因为jar包里面的代码无法打断点,可以将类反编译出来,jdk会优先调用classes目录的字节码。

    异常定位法


    既然是抛出了异常导致启动不成功,那么异常是哪里引起的,可以通过增加try…catch来捕获异常,定位异常点,记录异常信息。

    最终解决

    原来是Constants类调用了Lisences.jar里面的方法导致抛出异常而没有处理,导致程序异常中断,static块没有执行完成,类加载器无法
    初始化Constants实例。所以遗留系统里面的问题就是写了大量的没有异常处理机制的代码,既没有自主处理异常,也没有抛出异常,告知调用者
    可能会出现的异常,通过上述的四个方法基本上可以找到问题点,解决之。

    123

    盛富强

    blogs and research

    28 posts
    17 tags
    github
    © 2023 盛富强
    Powered by Hexo
    |
    Theme — NexT.Muse v5.1.4