# Introduction

一本简单的学习笔记，用来记录和分享自己的学习与成长之路。

**Author**: PegasusMeteor


# DesignPattern

   在最近的工作中发现自己对于一些基础知识的掌握一直浮于表面，因此打算拿出一些精力来进行查漏补缺。\
   网上对于设计模式的讲解一般是基于java语言的，而我最近工作中主要的编程语言是golang，所以我决定，对于每种设计模式，都以java和golang两种语言来实现。\
   在学习的过程中，我会尽量找一些源码来进行分析学习，并且验证设计模式在实际应用过程中的使用。

## 一些参考

* **Java版示例，以及设计模式中大部分资料整理，来自Geely老师《java设计模式精讲》** ，本系列实际上也是该课程的学习笔记。
* [《设计模式的艺术-刘伟》](https://blog.csdn.net/LoveLion/article/details/17517213)
* [golang-design-pattern](https://github.com/senghoo/golang-design-pattern)

## 其他补充

**TODO:**&#x8BBE;计模式系列已经完结，后期会结合实际的工作经验，对这里列举的23种设计模式进行补充和完善。例如实际案例，源码解析等等。


# 七大原则


# 开闭原则(OCP)

## 定义

   **开闭原则(Open-Closed Principle, OCP)** ：一个软件实体如类、模块和函数应当对扩展开放，对修改关闭。即软件实体应尽量在不修改原有代码的情况下进行扩展。\
用抽象构建框架，用实现扩展细节。\
优点：提高软件系统的可复用性和可维护性。

实现开闭原则的核心思想，就是面向抽象继承。

## Golang Demo

```go
package openclose

type CourseInterface interface {
  GetID() int
  GetName() string
  GetPrice() float32
}
```

```go
package openclose

type JavaCourse struct {
  id    int
  name  string
  price float32
}

func NewJavaCourse(id int, name string, price float32) *JavaCourse {
  return &JavaCourse{id: id, name: name, price: price}
}

func (j *JavaCourse) GetID() int {
  return j.id
}

func (j *JavaCourse) GetName() string {
  return j.name
}

func (j *JavaCourse) GetPrice() float32 {
  return j.price
}
```

```go
package openclose

type JavaDiscountCourse struct {
  JavaCourse
}

func NewJavaDiscountCourse(ID int, name string, price float32) *JavaDiscountCourse {
  return &JavaDiscountCourse{JavaCourse: *NewJavaCourse(ID, name, price)}
}

func (j *JavaDiscountCourse) GetOriginPrice() float32 {
  return j.GetPrice()
}

func (j *JavaDiscountCourse) GetDiscountPrice() float32 {
  return j.GetPrice() * 0.8
}
```

```go
package openclose

import (
    "testing"
)

func TestOpenClose(t *testing.T) {

    course := NewJavaDiscountCourse(1, "JavaEE", 10)
    t.Logf("\nid:%d,\n名称:%s,\n价格:%f,\n折后价格:%f\n", course.GetID(), course.GetName(), course.GetPrice(), course.GetDiscountPrice())

}
```

## Java Demo

```java
package tech.selinux.design.principle.openclose;

public interface ICourse {
  Integer getID();
  String getName();
  Double getPrice();
}
```

```java
package tech.selinux.design.principle.openclose;

public class JavaCourse implements ICourse {
  private Integer ID;
  private String Name;
  private Double Price;

  public JavaCourse(Integer id, String name, Double price) {
    this.ID = id;
    this.Name = name;
    this.Price = price;
  }

  @Override
  public Integer getID() {
    return ID;
  }

  @Override
  public String getName() {
    return Name;
  }

  @Override
  public Double getPrice() {
    return Price;
  }
}
```

```java
package tech.selinux.design.principle.openclose;

public class JavaDiscountCourse extends JavaCourse {

  public JavaDiscountCourse(Integer id, String name, Double price) {
    super(id, name, price);
  }
  public Double getOriginPrice() {
    return super.getPrice() ;
  }

  public Double getDiscountPrice() {
    return super.getPrice() * 0.8;
  }
}
```

```java
package tech.selinux.design.principle.openclose;

public class Test {
  public static void main(String[] args) {
    ICourse iCourse = new JavaDiscountCourse(96, "Java", 348d);
    JavaDiscountCourse javaCourse = (JavaDiscountCourse) iCourse;
    System.out.println(
        "ID:"
            + javaCourse.getID()
            + " 名称:"
            + javaCourse.getName()
            + " 原价:"
            + javaCourse.getOriginPrice()
            + " 折后价格:"
            + javaCourse.getDiscountPrice()
            + "元");
  }
}
```

## UML

![开闭原则的UML类图](/files/-LcWN6-3U2iO7sgMuiYD)

## 总结

从上面两个Demo 中，我们还能看出一个Java 和 Golang 的区别。Java中，虽然用子类示例化了接口，但是在使用子类的方法时，还是需要将接口转成子类，而Golang中没有这种要求。

### 补充另一个版本的Java/Scala Demo 以及源码解析

## Java Demo\_

## Scala Demo

## UML\_

## 源码解析


# 依赖倒置原则(DIP)

   **依赖倒置原则(Dependency Inversion Principle, DIP)**：高层模块不应该依赖于低层模块，二者应该依赖于其抽象。抽象不应该依赖于细节，细节应当依赖于抽象。换言之，要针对接口编程，而不是针对实现编程。

## Golang Demo

```go
package dependenceinversion

type CourseInterface interface {
    studyCourse()
}
```

```go
package dependenceinversion

import "fmt"

type JavaCourse struct {
}

func NewJavaCourse() *JavaCourse {
    return &JavaCourse{}
}

func (JavaCourse) studyCourse() {
    fmt.Println("study java")
}
```

```go
package dependenceinversion

import "fmt"

type PythonCourse struct {
}

func NewPythonCourse() *PythonCourse {
    return &PythonCourse{}
}

func (PythonCourse) studyCourse() {
    fmt.Println("study pyhon")
}
```

```go
package dependenceinversion

type Student struct {
    course CourseInterface
}

func NewStudent(course CourseInterface) *Student {
    return &Student{course: course}
}

func (s Student) studyCourse() {
    s.course.studyCourse()
}
```

```go
package dependenceinversion

import "testing"

func TestDependenceInversion(t *testing.T) {
    student := NewStudent(NewJavaCourse())
    student.studyCourse()
}
```

## Java Demo

```java
package tech.selinux.design.principle.dependenceinversion;

public interface ICourse {
  void studyCourse();
}
```

```java
package tech.selinux.design.principle.dependenceinversion;

public class JavaCourse implements ICourse {

  @Override
  public void studyCourse() {
    System.out.println("Study Java");
  }
}
```

```java
package tech.selinux.design.principle.dependenceinversion;

public class PythonCourse implements ICourse {
  @Override
  public void studyCourse() {
    System.out.println("Study Python");
  }
}
```

```java
package tech.selinux.design.principle.dependenceinversion;

public class Student {

  private ICourse iCourse;

  public void setiCourse(ICourse iCourse) {
    this.iCourse = iCourse;
  }

  public void studyCourse() {
    iCourse.studyCourse();
  }
}
```

```java
package tech.selinux.design.principle.dependenceinversion;

public class Test {
  public static void main(String[] args) {
    Student geely = new Student();
    geely.setiCourse(new JavaCourse());
    geely.studyCourse();
  }
}
```

## UML

![依赖倒置原则UML](/files/-LcWbo0DfOPPSutbfhzA)

### 补充另一个版本的Java/Scala Demo 以及源码解析

## Java Demo\_

## Scala Demo

## UML\_

## 源码解析


# 单一职责原则(SRP)

   **单一职责原则(Single Responsibility Principle, SRP)**：一个类只负责一个功能领域中的相应职责，或者可以定义为：就一个类而言，应该只有一个引起它变化的原因，不要存在多于一个导致类变更的原因。一个类/接口/方法只负责一项职责。\
   单一职责原则是高内聚低耦合的指导方针，可以降低类的复杂度，提高类的可读性，提高系统的可维护性、降低变更引起的风险。其实，通俗来理解，一个类不能太累。我们在传统的软件工程，或者老旧的系统中经常能够看到，一个代码上万行的类。这种情况可能是由于历史原因导致的，但是不可否认，维护起来难度简直太大。因此，我们在软件设计过程中，要尝试将职责进行分离，不通职责封装在不同的类中，这样就能够降低我们设计软件的复杂度了。\
   单一职责原则不仅仅适用于面向对象编程语言设计，只要是模块化的编程，都适用。

## Golang Demo

接下来，我们以接口为例，来看看单一职责原则在golang编程中的体现。详细的例子可以参考下面的Java Demo.

```go
package singleresponsibility

type CourseContent interface {
  getCourseName() string

  getCourseVideo() []byte
}
```

```go
package singleresponsibility

type CourseManager interface {
  studyCourse()

  refundCourse()
}
```

```go
package singleresponsibility

import "fmt"

type Course struct {
}

func (Course) studyCourse() {
  fmt.Println("study")
}

func (Course) refundCourse() {
  fmt.Println("refund")
}

func (Course) getCourseName() string {
  return "course name"
}

func (Course) getCourseVideo() []byte {
  return nil
}
```

## Java Demo

### 接口单一职责原则的体现

假设我们有下面这样一个接口。这个接口里面 既有获取信息，又有对课程的的操作等行为。

```java
package tech.selinux.design.principle.singleresponsibility;

public interface ICourse {
  String getCourseName();

  byte[] getCourseVideo();

  void studyCourse();

  void refundCourse();
}
```

我们就可以对接口中的行为进行分类，使得每个接口具有单一职责。实现类可以实现多个接口就可以了。

```java
package tech.selinux.design.principle.singleresponsibility;

public interface ICourseContent {

  String getCourseName();

  byte[] getCourseVideo();
}
```

```java
package tech.selinux.design.principle.singleresponsibility;

public interface ICourseManager {

  void studyCourse();

  void refundCourse();
}
```

```java
package tech.selinux.design.principle.singleresponsibility;

public class CourseImpl implements ICourseManager, ICourseContent {
  @Override
  public void studyCourse() {}

  @Override
  public void refundCourse() {}

  @Override
  public String getCourseName() {
    return null;
  }

  @Override
  public byte[] getCourseVideo() {
    return new byte[0];
  }
}
```

### 类单一职责原则的体现。

假设我们有下面一个类。

```java
package tech.selinux.design.principle.singleresponsibility;

public class Bird {
  public void mainMoveMode(String birdName) {
    if ("鸵鸟".equals(birdName)) {
      System.out.println(birdName + "用脚走");
    } else {
      System.out.println(birdName + "用翅膀飞");
    }
  }
}
```

如果后期，我们需要增加许多新的鸟类，那这个类将变的无比庞杂，所以，我们可以定义几个只有单一职责的类。

```java
package tech.selinux.design.principle.singleresponsibility;

public class FlyBird {
  public void mainMoveMode(String birdName) {
    System.out.println(birdName + "用翅膀飞");
  }
}
```

```java
package tech.selinux.design.principle.singleresponsibility;

public class WalkBird {
  public void mainMoveMode(String birdName) {
    System.out.println(birdName + "用脚走");
  }
}
```

```java
package tech.selinux.design.principle.singleresponsibility;

public class Test {
  public static void main(String[] args) {
    FlyBird flyBird = new FlyBird();
    flyBird.mainMoveMode("大雁");

    WalkBird walkBird = new WalkBird();
    walkBird.mainMoveMode("鸵鸟");
  }
}
```

## UML (接口为例)

![接口单一职责UML](/files/-LcYfJ0ZenDLw7O2ztfd)

### 补充另一个版本的Java/Scala Demo 以及源码解析

## Java Demo\_

## Scala Demo

## UML\_

## 源码解析


# 接口隔离原则(ISP)

**接口隔离原则(Interface Segregation Principle, ISP)**：使用多个专门的接口，而不使用单一的总接口，即客户端不应该依赖那些它不需要的接口。

* 一个类对一个类的依赖应该建立在最小的接口上
* 建立单一接口不要建立庞大臃肿的接口
* 尽量细化接口，接口中的方法应该尽量少

**在使用接口隔离原则时，我们需要注意控制接口的粒度，接口不能太小，如果太小会导致系统中接口泛滥，不利于维护；接口也不能太大，太大的接口将违背接口隔离原则，灵活性较差，使用起来很不方便。**&#x4E00;般而言，接口中仅包含为某一类用户定制的方法即可，不应该强迫客户依赖于那些它们不用的方法。这符合我们经常说的，高内聚，低耦合的思想。从而使程序具有很好的可读性，可维护性，可扩展性。

## Golang Demo

```go
package interfacesegregation

// 生产中根据实际情况，将接口拆分到不同的文件中
type EatAnimalAction interface {
    eat()
}
type FlyAnimalAction interface {
    fly()
}
type SwimAnimalAction interface {
    swim()
}
```

```go
package interfacesegregation

import "fmt"

type Bird struct {
}

func (Bird) fly() {
    fmt.Println("bird fly")
}

func (Bird) eat() {
    fmt.Println("bird eat")
}
```

```go
package interfacesegregation

import "fmt"

type Dog struct {
}

func (Dog) swim() {
    fmt.Println("dog swim")
}

func (Dog) eat() {
    fmt.Println("dog eat")
}
```

## Java Demo

假设我们有这样一个接口，定义了动物的行为。

```java
package tech.selinux.design.principle.interfacesegregation;

public interface IAnimalAction {
  void eat();

  void fly();

  void swim();
}
```

但是，如果我们有一个实现类，例如Dog,并不需要实现fly的方法，因此这个接口中定义的方法就显得冗余复杂。这时接口隔离原则的作用就体现出来了。

```java
package tech.selinux.design.principle.interfacesegregation;

public interface IEatAnimalAction {
  void eat();
}
```

```java
package tech.selinux.design.principle.interfacesegregation;

public interface IFlyAnimalAction {
  void fly();
}
```

```java
package tech.selinux.design.principle.interfacesegregation;

public interface ISwimAnimalAction {
  void swim();
}
```

```java
package tech.selinux.design.principle.interfacesegregation;

public class Dog implements ISwimAnimalAction, IEatAnimalAction {

  @Override
  public void eat() {}

  @Override
  public void swim() {}
}
```

```java
package tech.selinux.design.principle.interfacesegregation;

public class Bird implements IEatAnimalAction,IFlyAnimalAction {
  @Override
  public void eat() {}

  @Override
  public void fly() {}

}
```

## UML

![接口隔离原则UML](/files/-LcZ6HrWYuOZgogcmaBj)

### 补充另一个版本的Java/Scala Demo 以及源码解析

## Java Demo\_

## Scala Demo

## UML\_

## 源码解析


# 迪米特法则(LoD)

   **迪米特法则(Law of Demeter, LoD)**：一个对象应该对其他对象保持最少的了解，又叫最少知道原则。

   迪米特法则要求我们在设计系统时，应该尽量减少对象之间的交互，如果两个对象之间不必彼此直接通信，那么这两个对象就不应当发生任何直接的相互作用，如果其中的一个对象需要调用另一个对象的某一个方法的话，可以通过第三者转发这个调用。简言之，就是通过引入一个合理的第三者来降低现有对象之间的耦合度。

   迪米特法则强调**不要和“陌生人”说话、只与你的直接朋友通信**，在迪米特法则中，对于一个对象，其朋友包括以下几类：

* 当前对象本身(this)；
* 以参数形式传入到当前对象方法中的对象；
* 当前对象的成员对象；
* 如果当前对象的成员对象是一个集合，那么集合中的元素也都是朋友；
* 当前对象所创建的对象。

## Golang Demo

```go
package demeter

type Member struct {
}

func NewMember() *Member {
    return &Member{}
}
```

```go
import (
    "container/list"
    "fmt"
)

type TeamLeader struct {
}

func NewTeamLeader() *TeamLeader {
    return &TeamLeader{}
}

func (TeamLeader) checkNumberOfMember() {
    l := list.New()
    l.Init()
    for i := 0; i < 20; i++ {
        l.PushBack(NewMember())
    }
    fmt.Println(l.Len())
}
```

```go
package demeter

type Boss struct {
}

func NewBoss() *Boss {
    return &Boss{}
}

func (Boss) commandCheckNumber(leader *TeamLeader) {

}
```

```go
package demeter

import "testing"

func Test(t *testing.T) {
    boss := NewBoss()
    boss.commandCheckNumber(NewTeamLeader())
}
```

## Java Demo

```java
package tech.selinux.design.principle.demeter;

public class Boss {
  public void commandCheckNumber(TeamLeader teamLeader) {
    teamLeader.checkNumberOfMember();
  }
}
```

```java
package tech.selinux.design.principle.demeter;

public class Member {}
```

```java
package tech.selinux.design.principle.demeter;

import java.util.ArrayList;
import java.util.List;

public class TeamLeader {
  public void checkNumberOfMember() {
    List<Member> memberList = new ArrayList<Member>();
    for (int i = 0; i < 20; i++) {
      memberList.add(new Member());
    }
    System.out.println("组内成员数量" + memberList.size());
  }
}
```

```java
package tech.selinux.design.principle.demeter;

public class Test {
  public static void main(String[] args) {
    Boss boss = new Boss();
    TeamLeader teamLeader = new TeamLeader();
    boss.commandCheckNumber(teamLeader);
  }
}
```

## UML

这里的类结构关系比较简单，就不上图了。

### 补充另一个版本的Java/Scala Demo 以及源码解析

## Java Demo\_

## Scala Demo

## UML\_

## 源码解析


# 里氏代换原则(LSP)

   **里氏代换原则(Liskov Substitution Principle, LSP)**：所有引用基类（父类）的地方必须能透明地使用其子类的对象。\
   里氏代换原则的严格表述如下：如果对每一个类型为S的对象o1，都有类型为T的对象o2，使得以T定义的所有程序P在所有的对象o1代换o2时，程序P的行为没有变化，那么类型S是类型T的子类型。\
   **定义拓展**：一个软件实体如果适用一个父类的话，那么就一定适用其子类，所有引用父类的地方必须能够透明地使用其子类的对象，子类对象能够替换父类对象，而程序逻辑不变。里氏代换原则告诉我们，**在软件中将一个基类对象替换成它的子类对象，程序将不会产生任何错误和异常，反过来则不成立，如果一个软件实体使用的是一个子类对象的话，那么它不一定能够使用基类对象**。例如：我喜欢动物，那我一定喜欢狗，因为狗是动物的子类；但是我喜欢狗，不能据此断定我喜欢动物，因为我并不喜欢老鼠，虽然它也是动物。\
   **引申意义**：子类可以扩展父类的功能，但是不能修改父类的功能。

* 含义1:子类可以实现父类的抽象方法，但是不能覆盖父类的非抽象方法。
* 含义2:子类中可以增加自己特有的方法。
* 含义3:当子类方法重载父类方法时，方法的前置条件(即方法的入参、输入)要比父类方法的输入参数更宽松。
* 含义4:当子类的方法实现父类的方法时(重写/重载或实现抽象方法)，方法的后置条件(即方法的输出/返回值)要比父类更严格或相等。

里氏代换原则有众多优点：

* 约束继承泛滥，开闭原则的一种体现。
* 加强程序的健壮性，同时在变更时也可以做到非常好的兼容性，提高程序的维护性、扩展性。降低需求变更时引入的风险。

下面，我们来看一个demo。假设，长方形是正方形的基类，长方形有一个方法叫做resize(),这个方法的作用是如果宽度小于长度，就重新调整大小，使宽度大于长度一个单位。正方形也可以调用这个方法，但是却改变了自己是正方形的事实。这就违背了程序行为没有变化这一条件。

## Golang Demo

```go
package liskovsubstitution

type QuardRangle interface {
    Width() int
    Length() int
}
```

```go
package liskovsubstitution

type Rectangle struct {
    length int
    width  int
}

func (r *Rectangle) SetWidth(width int) {
    r.width = width
}

func (r Rectangle) SetLength(length int) {
    r.length = length
}

func (r Rectangle) Width() int {
    return r.width
}

func (r Rectangle) Length() int {
    return r.length
}
```

```go
package liskovsubstitution

type Square struct {
    sideLength int
}

func NewSquare(sideLength int) *Square {
    return &Square{sideLength: sideLength}
}

func (s *Square) SetSideLength(sideLength int) {
    s.sideLength = sideLength
}

func (s Square) Width() int {
    return s.sideLength
}

func (s Square) Length() int {
    return s.sideLength
}
```

```go
package liskovsubstitution

import (
    "fmt"
    "testing"
)

func Test(t *testing.T) {
    square := NewSquare(10)

    //resize(square)
}
func resize(rectangle Rectangle) {
    for rectangle.Width() <= rectangle.Length() {
        rectangle.SetWidth(rectangle.Width() + 1)
        fmt.Printf("width:%d,length:%d", rectangle.Width(), rectangle.Length())
    }
}
```

## Java Demo

```java
package tech.selinux.design.principle.liskovsubstitution;

public interface Quadrangle {
    long getWidth();
    long getLength();
}
```

```java
package tech.selinux.design.principle.liskovsubstitution;


public class Rectangle implements Quadrangle {
    private long length;
    private long width;

    @Override
    public long getWidth() {
        return width;
    }

    @Override
    public long getLength() {
        return length;
    }

    public void setLength(long length) {
        this.length = length;
    }

    public void setWidth(long width) {
        this.width = width;
    }
}
```

```java
package tech.selinux.design.principle.liskovsubstitution;


public class Square implements Quadrangle {
    private long sideLength;

    public long getSideLength() {
        return sideLength;
    }

    public void setSideLength(long sideLength) {
        this.sideLength = sideLength;
    }

    @Override
    public long getWidth() {
        return sideLength;
    }

    @Override
    public long getLength() {
        return sideLength;
    }
}
```

```java
package tech.selinux.design.principle.liskovsubstitution;

public class Test {
    public static void resize(Rectangle rectangle){
        while (rectangle.getWidth() <= rectangle.getLength()){
            rectangle.setWidth(rectangle.getWidth()+1);
            System.out.println("width:"+rectangle.getWidth() + " length:"+rectangle.getLength());
        }
        System.out.println("resize方法结束 width:"+rectangle.getWidth() + " length:"+rectangle.getLength());
    }

//    public static void main(String[] args) {
//        Rectangle rectangle = new Rectangle();
//        rectangle.setWidth(10);
//        rectangle.setLength(20);
//        resize(rectangle);
//    }
    public static void main(String[] args) {
        Square square = new Square();
//        square.setLength(10);
//        resize(square);
    }
}
```

### 补充另一个版本的Java/Scala Demo 以及源码解析

## Java Demo\_

## Scala Demo

## UML\_

## 源码解析


# 合成复用原则(CRP)

   **合成复用原则(Composite Reuse Principle, CRP)**：尽量使用对象组合/聚合，而不是继承来达到复用的目的。\
   **优点**： 使系统更加灵活，降低类于类之间的耦合度，一个类的变化对其他类造成的影响相对较小。\
   **缺点**： 破坏了包装，同时包含的类的实现细节被隐藏。

## 聚合/组合

   组合和聚合到底有什么区别呢？下面我们拿出一点时间来对这两个概念进行一下简单的区分。下面我们引入一个代码场景。 A类中包含了B类的一个引用，当A的一个对象消失时，B对象所指向的这个引用也就随之消失了，因为没有任何一个引用指向它，就被GC了。这种情况就叫做组合。反过来想，B类创建的对象，所指向的引用，如果还有另外一个引用指向它，这种情况就叫做聚合。因为A类消失后，B类所创建的对象没有消失。 也就是说，组合关系的类，具有同样的生命周期。

下面我们从代码上区分一下聚合和组合的关系。

```java
public  class BirdGroup
{
    public Bird bird;

    public BirdGroup(Bird bird)
    {
        this.bird = bird;
    }
}
```

聚合关系的类里有另外一个类作为参数。BirdGroup类被gc之后，bird类的引用依然建在。这就是聚合。

```java
public class Bird
{
    public Wings wings;

    public Bird()
    {
        wings=new Wings();
    }
}
```

组合关系的类里有另外一个类的实例化，如果Bird这个类被GC了，内部的类的引用，随之消失了，这就是组合。

## Golang Demo

```go
package compositionaggregation

type DBConnection interface {
    GetConnection() string
}
```

```go
package compositionaggregation

type MySQLConnection struct {
}

func NewMySQLConnection() *MySQLConnection {
    return &MySQLConnection{}
}

func (MySQLConnection) GetConnection() string {
    return "mysql conn"
}
```

```go
package compositionaggregation

type PostgreSQLConnection struct {
}

func NewPostgreSQLConnection() *PostgreSQLConnection {
    return &PostgreSQLConnection{}
}

func (PostgreSQLConnection) GetConnection() string {
    return "postgresql conn"
}
```

```go
package compositionaggregation

import "fmt"

type ProductDao struct {
    dbConn DBConnection
}

func NewProductDao(dbConn DBConnection) *ProductDao {
    return &ProductDao{dbConn: dbConn}
}

func (p ProductDao) addProdcut() {
    conn := p.dbConn.GetConnection()

    fmt.Println("add " + conn)
}
```

```go
package compositionaggregation

import "testing"

func Test(t *testing.T) {
    product := NewProductDao(NewMySQLConnection())
    product.addProdcut()
}
```

## Java Demo

```java
package tech.selinux.design.principle.compositionaggregation;

public abstract class DBConnection {
    public abstract String getConnection();
}
```

```java
package tech.selinux.design.principle.compositionaggregation;

public class MySQLConnection extends DBConnection {
    @Override
    public String getConnection() {
        return "MySQL conn";
    }
}
```

```java
package tech.selinux.design.principle.compositionaggregation;

public class PostgreSQLConnection extends DBConnection {
    @Override
    public String getConnection() {
        return "PostgreSQL conn";
    }
}
```

```java
package tech.selinux.design.principle.compositionaggregation;

public class ProductDao{
    private DBConnection dbConnection;

    public void setDbConnection(DBConnection dbConnection) {
        this.dbConnection = dbConnection;
    }

    public void addProduct() {
        String conn = dbConnection.getConnection();
        System.out.println("add" + conn);
    }
}
```

```java
package tech.selinux.design.principle.compositionaggregation;

public class Test {
    public static void main(String[] args) {
        ProductDao productDao = new ProductDao();
        productDao.setDbConnection(new PostgreSQLConnection());
        productDao.addProduct();
    }
}
```

## UML

![合成复用原则UML](/files/-Lc_H8xhjgF3Vv1SqYZs)

### 补充另一个版本的Java/Scala Demo 以及源码解析

## Java Demo\_

## Scala Demo

## UML\_

## 源码解析


# 创建型模式


# 简单工厂模式

   **简单工厂模式(Simple Factory Pattern)**：由一个工厂对象决定创建哪一种产品类的实例。\
   简单工厂模式是工厂模式的“小弟”，它不属于GoF23种设计模式之一。但是平常应用也比较频繁，所以我们先介绍它。

## 优点

只要传入一个正确的参数，就可以获得你所需要的对象，无须知道其创建细节。

## 缺点

工厂类的职责相对过重，增加新的产品，需要修改工厂类的判断逻辑，违背了开闭原则。

## Golang Demo

```go
package simplefactory

import "fmt"

type JavaVideo struct {
}

func (JavaVideo) produce() {
    fmt.Println("produce java video")
}
```

```go
package simplefactory

import "fmt"

type PythonVideo struct {
}

func (PythonVideo) produce() {
    fmt.Println("produce python video")
}
```

```go
package simplefactory

type Video interface {
    produce()
}

func GetVideoByType(v Video) (video Video) {
    switch v.(type) {
    case JavaVideo:
        video = JavaVideo{}
    case PythonVideo:
        video = PythonVideo{}
    }
    return
}
func GetVideoByStr(class string) (video Video) {
    switch class {
    case "java":
        video = JavaVideo{}
    case "python":
        video = PythonVideo{}
    }
    return
}
```

```go
package simplefactory

import (
    "testing"
)

func TestGetVideoByStr(t *testing.T) {
    video := GetVideoByStr("java")
    video.produce()

}

func TestGetVideoByType(t *testing.T) {
    video := GetVideoByType(PythonVideo{})
    video.produce()
}
```

## Java Demo

```java
package tech.selinux.design.pattern.creational.simplefactory;

public abstract class Video {
    public abstract void produce();
}
```

```java
package tech.selinux.design.pattern.creational.simplefactory;

public class JavaVideo extends Video {
    @Override
    public void produce() {
        System.out.println("produce java");
    }
}
```

```java
package tech.selinux.design.pattern.creational.simplefactory;

public class PythonVideo extends Video {
    @Override
    public void produce() {
        System.out.println("produce python");
    }
}
```

```java
package tech.selinux.design.pattern.creational.simplefactory;

public class VideoFactory {
    public Video getVideo(Class c){
        Video video = null;
        try {
            video = (Video) Class.forName(c.getName()).newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return video;
    }


    public Video getVideo(String type){
        if("java".equalsIgnoreCase(type)){
            return new JavaVideo();
        }else if("python".equalsIgnoreCase(type)){
            return new PythonVideo();
        }
        return null;
    }

}
```

```java
package tech.selinux.design.pattern.creational.simplefactory;

public class Test {
    public static void main(String[] args) {

        VideoFactory videoFactory = new VideoFactory();
        Video video = videoFactory.getVideo(JavaVideo.class);
        if(video == null){
            return;
        }
        video.produce();
    }
}
```

## UML

![简单工厂模式UML](/files/-Lc_aIJ1cpqIbrZl5y8k)

### 补充另一个版本的Java/Scala Demo 以及源码解析

## Java Demo\_

## Scala Demo

## UML\_

## 源码解析


# 工厂方法模式

   前面我们介绍过，简单工厂模式最大的缺点是当有新产品要加入到系统中时，必须修改工厂类，需要在其中加入必要的业务逻辑，这违背了“开闭原则”。

   在工厂方法模式中，我们不再提供一个统一的工厂类来创建所有的产品对象，而是针对不同的产品提供不同的工厂，系统提供一个与产品等级结构对应的工厂等级结构。

   **工厂方法模式(Factory Method Pattern)**：定义一个用于创建对象的接口，让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类。工厂方法模式又简称为工厂模式(Factory Pattern)，又可称作虚拟构造器模式(Virtual Constructor Pattern)或多态工厂模式(Polymorphic Factory Pattern)。工厂方法模式是一种类创建型模式。

## 适用场景

* 创建对象需要大量重复的代码
* 客户端(应用层)不依赖于产品实例，如何被创建、实现等细节
* 一个类通过其子类来指定创建哪个对象

## 优点

* 用户只需要关心所需产品对应的工厂，无须关心创建细节
* 加入新产品符合开闭原则，提高可扩展性

## 缺点

* 类的个数容易过多，增加复杂度
* 增加了系统的抽象性和理解难度

## Golang Demo

```go
package factorymethod

type Video interface {
    produce()
}
```

```go
package factorymethod

type VideoFactory interface {
    getVideo() Video
}
```

```go
package factorymethod

import "fmt"

type JavaVideo struct {
}

func (j JavaVideo) produce() {
    fmt.Println("produce java")
}
```

```go
package factorymethod

type JavaFactory struct {
}

func NewJavaFactory() *JavaFactory {
    return &JavaFactory{}
}

func (j JavaFactory) getVideo() Video {
    return JavaVideo{}
}
```

```go
package factorymethod

import "fmt"

type PythonVideo struct {
}

func (PythonVideo) produce() {
    fmt.Println("produce python")
}
```

```go
package factorymethod

type PythonFactory struct {
}

func NewPythonFactory() *PythonFactory {
    return &PythonFactory{}
}

func (PythonFactory) getVideo() Video {
    return PythonVideo{}
}
```

```go
package factorymethod

import "testing"

func TestGetVideo(t *testing.T) {
    videoFactory := NewJavaFactory()
    videoFactory.getVideo().produce()
    videoFactory2 := NewPythonFactory()
    videoFactory2.getVideo().produce()
}
```

## Java Demo

```java
package tech.selinux.design.pattern.creational.factorymethod;

public abstract class Video {
    public abstract void produce();
}
```

```java
package tech.selinux.design.pattern.creational.factorymethod;

public abstract class VideoFactory {
    public abstract Video getVideo();
}
```

```java
package tech.selinux.design.pattern.creational.factorymethod;

public class JavaVideo extends Video {
    @Override
    public void produce() {
        System.out.println("Java Produce");
    }
}
```

```java
package tech.selinux.design.pattern.creational.factorymethod;

public class JavaVideoFactory extends VideoFactory {
    @Override
    public Video getVideo() {
        return new JavaVideo();
    }
}
```

```java
package tech.selinux.design.pattern.creational.factorymethod;

public class PythonVideo extends Video {
    @Override
    public void produce() {
        System.out.println("Python produce");
    }
}
```

```java
package tech.selinux.design.pattern.creational.factorymethod;

public class PythonVideoFactory extends VideoFactory {
    @Override
    public Video getVideo() {
        return new PythonVideo();
    }
}
```

```java
package tech.selinux.design.pattern.creational.factorymethod;

public class Test {
    public static void main(String[] args) {
        VideoFactory videoFactory = new PythonVideoFactory();
        VideoFactory videoFactory2 = new JavaVideoFactory();
        Video video = videoFactory.getVideo();
        video.produce();
    }
}
```

## UML

![工厂方法模式UML](/files/-Lcac7W7-wjbr6i3c6d-)

### 补充另一个版本的Java/Scala Demo 以及源码解析

## Java Demo\_

## Scala Demo

## UML\_

## 源码解析


# 抽象工厂模式

   **抽象工厂模式(Abstract Factory Pattern)**：提供一个创建一系列相关或相互依赖对象的接口，而无须指定它们具体的类。抽象工厂模式又称为Kit模式，它是一种对象创建型模式。

## 适用场景

* 客户端(应用层)不依赖于产品实例，如何被创建、实现等细节
* 强调一系列相关的产品对象（属于同一产品族）一起使用创建对象需要大量重复的代码
* 提供一个产品类的库，所有的产品以同样的接口出现，从而使客户端不依赖于具体实现

## 优点

* 具体产品在应用层代码隔离，无须关心创建细节
* 将一个系列的产品族统一到一起创建

## 缺点

* 规定了所有可能被创建的产品的集合，产品族中扩展新的产品困难，需要修改抽象工厂的接口
* 增加了系统的抽象性和理解难度

## 产品等级和产品族

![产品等级和产品族](/files/-LcaqepAOZQftMMmI4-s)

> (1) 产品等级结构：产品等级结构即产品的继承结构，如一个抽象类是电视机，其子类有海尔电视机、海信电视机、TCL电视机，则抽象电视机与具体品牌的电视机之间构成了一个产品等级结构，抽象电视机是父类，而具体品牌的电视机是其子类。\
> (2) 产品族：在抽象工厂模式中，产品族是指由同一个工厂生产的，位于不同产品等级结构中的一组产品，如海尔电器工厂生产的海尔电视机、海尔电冰箱，海尔电视机位于电视机产品等级结构中，海尔电冰箱位于电冰箱产品等级结构中，海尔电视机、海尔电冰箱构成了一个产品族。\
> <https://blog.csdn.net/lovelion/article/details/9319323>

## Golang Demo

定义一个产品族以及抽象工厂

```go
package abstractfactory

type Article interface {
    produce()
}

type Video interface {
    produce()
}

type CourseFactory interface {
    getVideo() Video
    getArticle() Article
}
```

```go
package abstractfactory

import "fmt"

type JavaVideo struct {
}

func (JavaVideo) produce() {
    fmt.Println("produce java")
}

type JavaArticle struct {
}

func (JavaArticle) produce() {
    fmt.Println("java 笔记")
}

type JavaCourseFactory struct {
}

func NewJavaCourseFactory() *JavaCourseFactory {
    return &JavaCourseFactory{}
}

func (JavaCourseFactory) getVideo() Video {
    return JavaVideo{}
}

func (JavaCourseFactory) getArticle() Article {
    return JavaArticle{}
}
```

```go
package abstractfactory

import "fmt"

type PythonVideo struct {
}

func (PythonVideo) produce() {
    fmt.Println("produce python")
}

type PythonArticle struct {
}

func (PythonArticle) produce() {
    fmt.Println("python 笔记")
}

type PythonCourseFactory struct {
}

func NewPythonCourseFactory() *PythonCourseFactory {
    return &PythonCourseFactory{}
}

func (PythonCourseFactory) getVideo() Video {
    return PythonVideo{}
}

func (PythonCourseFactory) getArticle() Article {
    return PythonArticle{}
}
```

```go
package abstractfactory

import "testing"

func TestAbstractFactory(t *testing.T) {

    var courseFactory CourseFactory = NewJavaCourseFactory()
    courseFactory.getVideo().produce()
    courseFactory.getArticle().produce()

}
```

## Java Demo

```java
package tech.selinux.design.pattern.creational.abstractfactory;

public abstract class Video {
    public abstract void produce();
}
```

```java
package tech.selinux.design.pattern.creational.abstractfactory;

public abstract class Article {
    public abstract void produce();
}
```

```java
package tech.selinux.design.pattern.creational.abstractfactory;

public interface CourseFactory {
    Video getVideo();
    Article getArticle();
}
```

```java
package tech.selinux.design.pattern.creational.abstractfactory;

public class JavaVideo extends Video {
    @Override
    public void produce() {
        System.out.println("produce java");
    }
}
```

```java
package tech.selinux.design.pattern.creational.abstractfactory;

public class JavaArticle extends Article {
    @Override
    public void produce() {
        System.out.println("Java 笔记");
    }
}
```

```java
package tech.selinux.design.pattern.creational.abstractfactory;

public class JavaCourseFactory implements CourseFactory {
    @Override
    public Video getVideo() {
        return new JavaVideo();
    }

    @Override
    public Article getArticle() {
        return new JavaArticle();
    }
}
```

```java
package tech.selinux.design.pattern.creational.abstractfactory;

public class PythonVideo extends Video {
    @Override
    public void produce() {
        System.out.println("produce python");
    }
}
```

```java
package tech.selinux.design.pattern.creational.abstractfactory;

public class PythonArticle extends Article {
    @Override
    public void produce() {
        System.out.println("Python 笔记");
    }
}
```

```java
package tech.selinux.design.pattern.creational.abstractfactory;

public class PythonCourseFactory implements CourseFactory {
    @Override
    public Video getVideo() {
        return new PythonVideo();
    }

    @Override
    public Article getArticle() {
        return new PythonArticle();
    }
}
```

```java
package tech.selinux.design.pattern.creational.abstractfactory;

public class Test {
    public static void main(String[] args) {
        CourseFactory courseFactory = new JavaCourseFactory();
        Video video = courseFactory.getVideo();
        Article article = courseFactory.getArticle();
        video.produce();
        article.produce();
    }
}
```

## UML

![抽象工厂模式UML](/files/-LcaqepMSx6xPDetd-HK)

### 补充另一个版本的Java/Scala Demo 以及源码解析

## Java Demo\_

## Scala Demo

## UML\_

## 源码解析


# 建造者模式

   **建造者模式(Builder Pattern)**：将一个复杂对象的构建与它的表示分离，使得同样的构建过程可以创建不同的表示。建造者模式是一种对象创建型模式。\
   用户只需指定需要建造的类型就可以得到他们，建造过程以及细节不需要知道。

## 适用场景

* 一个对象有非常复杂的内部结构（很多属性）
* 想把复杂对象的创建和使用分离

## 优点

* 封装性好，创建和使用分离
* 扩展性好，建造类之间独立，一定程度可以解偶

## 缺点

* 产生多余的Builder对象
* 产品内部发生变化，建造者都要修改，成本较大

## 建造者模式与工厂模式的比较

* 建造者模式更注重于方法的调用顺序，工厂模式注重于创建产品
* 创建对象的力度有所不同，建造者模式可以创建一些复杂的产品，由各种复杂的部件组成，而工厂模式创建出来的产品都是一个样子
* 关注点有区别，工厂模式注重的是只要把对象创建出来就ok了，而建造者模式不仅要创建产品，还要知道产品都是由哪些部件组成的
* 建造着模式关心顺序，而工厂不关心顺序

## Golang Demo

```go
package builder

type Builder interface {
    buildCourseName(courseName string) Builder
    buildCoursePPT(coursePPT string) Builder
    buildCourseVideo(courseVideo string) Builder
    buildCourseArticle(courseArticle string) Builder
    buildCourseQA(courseQA string) Builder
    build() Course
}
```

```go
package builder

type Course struct {
    courseName    string
    coursePPT     string
    courseVideo   string
    courseArticle string
    courseQA      string
}

type CourseBuilder struct {
    course Course
}

func NewCourseBuilder() *CourseBuilder {
    return &CourseBuilder{}
}

func (c *CourseBuilder) buildCourseName(courseName string) Builder {
    c.course.courseName = courseName
    return c
}

func (c *CourseBuilder) buildCoursePPT(coursePPT string) Builder {
    c.course.coursePPT = coursePPT
    return c
}

func (c *CourseBuilder) buildCourseVideo(courseVideo string) Builder {
    c.course.courseVideo = courseVideo
    return c
}

func (c *CourseBuilder) buildCourseArticle(courseArticle string) Builder {
    c.course.courseArticle = courseArticle
    return c
}

func (c *CourseBuilder) buildCourseQA(courseQA string) Builder {
    c.course.courseQA = courseQA
    return c
}

func (c *CourseBuilder) build() Course {
    return c.course
}
```

```go
package builder

import (
    "fmt"
    "testing"
)

func TestBuilder(t *testing.T) {

    course := NewCourseBuilder().
        buildCourseName("golang").
        buildCourseArticle("golang note").
        buildCoursePPT("golang ppt").build()
    fmt.Println(course)
}
```

## Java Demo

```java
package tech.selinux.design.pattern.creational.builder;

public class Course {

    private String courseName;
    private String coursePPT;
    private String courseVideo;
    private String courseArticle;

    //question & answer
    private String courseQA;

    public Course(CourseBuilder courseBuilder) {
        this.courseName = courseBuilder.courseName;
        this.coursePPT = courseBuilder.coursePPT;
        this.courseVideo = courseBuilder.courseVideo;
        this.courseArticle = courseBuilder.courseArticle;
        this.courseQA = courseBuilder.courseQA;
    }


    @Override
    public String toString() {
        return "Course{" +
                "courseName='" + courseName + '\'' +
                ", coursePPT='" + coursePPT + '\'' +
                ", courseVideo='" + courseVideo + '\'' +
                ", courseArticle='" + courseArticle + '\'' +
                ", courseQA='" + courseQA + '\'' +
                '}';
    }

    public static class CourseBuilder{
        private String courseName;
        private String coursePPT;
        private String courseVideo;
        private String courseArticle;

        //question & answer
        private String courseQA;

        public CourseBuilder buildCourseName(String courseName){
            this.courseName = courseName;
            return this;
        }


        public CourseBuilder buildCoursePPT(String coursePPT) {
            this.coursePPT = coursePPT;
            return this;
        }

        public CourseBuilder buildCourseVideo(String courseVideo) {
            this.courseVideo = courseVideo;
            return this;
        }

        public CourseBuilder buildCourseArticle(String courseArticle) {
            this.courseArticle = courseArticle;
            return this;
        }

        public CourseBuilder buildCourseQA(String courseQA) {
            this.courseQA = courseQA;
            return this;
        }

        public Course build(){
            return new Course(this);
        }
    }
}
```

```java
package tech.selinux.design.pattern.creational.builder;

public class Test {
  public static void main(String[] args) {
    // 链式调用
    Course course =
        new Course.CourseBuilder()
            .buildCourseName("Java pattern")
            .buildCoursePPT("Java pattern PPT")
            .buildCourseVideo("Java pattern video")
            .build();
    System.out.println(course);
  }
}
```

## UML

![建造者模式UML](/files/-LcdZ0JpBNx2Ln6GIZ5x)

### 补充另一个版本的Java/Scala Demo 以及源码解析

## Java Demo\_

## Scala Demo

## UML\_

## 源码解析


# 单例模式

**单例模式(Singleton Pattern)**：确保某一个类只有一个实例，而且自行实例化并向整个系统提供这个实例，这个类称为单例类，它提供全局访问的方法。单例模式是一种对象创建型模式。

## 优点

* 在内存里只有一个实例，减少了内存开销
* 可以避免对资源的多重占用
* 设置全局访问点，严格控制访问

## 缺点

* 没有接口，扩展比较困难。

## 重点

* 私有构造器
* 线程安全
* 延迟加载
* 序列化和反序列化安全
* 反射
* DoubleCheck

单例模式有很多种，接下来我们通过演进式的方式来一步一步地实现单例模式的理解。

## 懒汉式单例

### Golang Demo 1

```go
package singleton

type LazySingleton struct {
}

var singleton *LazySingleton

func GetInstense() *LazySingleton {
    if singleton == nil {
        singleton = &LazySingleton{}
    }
    return singleton
}
```

### Java Demo 1

```java
package tech.selinux.design.pattern.creational.singleton;

public class LazySingleton {
  private static LazySingleton lazySingleton = null;

  private LazySingleton() {}

  public static LazySingleton getInstance() {
    if (lazySingleton == null) {
      lazySingleton = new LazySingleton();
    }
    return lazySingleton;
  }
}
```

懒汉式单例模式是最基础的一种写法，但是如果我们在并发环境下去调用GetInstance()的时候，可能会创建出多个instance，这样就违背了我们单例的初衷，所以我们对这个方法进行一个加锁机制，这样就能够保证，每次调用时首先会获取锁资源，保证了实例的单一性。

### Golang Demo 2

```go
package singleton

import "sync"

type LazySingleton struct {
}

var singleton *LazySingleton

var lock sync.Mutex

func GetInstense() *LazySingleton {
    lock.Lock()
    defer lock.Unlock()
    if singleton == nil {
        singleton = &LazySingleton{}
    }
    return singleton
}
```

### Java Demo 2

```java
package tech.selinux.design.pattern.creational.singleton;

public class LazySingleton {
  private static LazySingleton lazySingleton = null;

  private LazySingleton() {}

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

  //  public static synchronized LazySingleton getInstance() {
  //
  //    if (lazySingleton == null) {
  //      lazySingleton = new LazySingleton();
  //    }
  //    return lazySingleton;
  //  }
}
```

代码做了简单的修改了，引入了锁的机制，在GetInstance函数中，每次调用我们都会上一把锁，保证只有一个goroutine执行它，这个时候并发的问题就解决了。不过现在不管什么情况下都会上一把锁，而且加锁的代价是很大的，有没有办法继续对我们的代码进行进一步的优化呢？

## DoubleCheck 懒汉式

### Golang Demo

```go
package singleton

import "sync"

type LazySingleton struct {
}

var singleton *LazySingleton

var lock sync.Mutex

func GetInstense() *LazySingleton {
    if singleton == nil {
        lock.Lock()
        defer lock.Unlock()
        if singleton == nil {
            singleton = &LazySingleton{}
        }
    }

    return singleton
}
```

在上面的方法中，我们做了很多的工作就是为了在并发模式下让，GetInstance中的实例只创建一次。其实在golang中，有一个比较优雅的方式 once.Do 下面我们练下demo。

```go
package singleton

import "sync"

type LazySingleton struct {
}

var singleton *LazySingleton
var once sync.Once

func GetInstense() *LazySingleton {
    once.Do(func() {
        singleton = &LazySingleton{}
    })
    return singleton
}
```

### Java Demo

```java
package tech.selinux.design.pattern.creational.singleton;

public class LazyDoubleCheckSingleton {
   // volatile 能够解决多线程访问的有序性
  private static volatile LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;

  private LazyDoubleCheckSingleton() {}

  public static LazyDoubleCheckSingleton getInstance() {
    if (lazyDoubleCheckSingleton == null) {
      synchronized (LazyDoubleCheckSingleton.class) {
        if (lazyDoubleCheckSingleton == null) {
          lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
        }
      }
    }
    return lazyDoubleCheckSingleton;
  }
}
```

## 静态内部类

对于java 而言，还可以使用静态内部类来实现。jvm 在类的初始化阶段（也就是class被加载后，并且被线程使用之前）,jvm 会取获取一个锁，这个锁可以同步多个线程对一个类的初始化。基于这个特性，我们可以实现基于静态内部类的，并且是线程安全的延迟初始化加载方案。

```java
package tech.selinux.design.pattern.creational.singleton;

public class StaticInnerClassSingleton {
  private static class InnerClass {
    private static StaticInnerClassSingleton staticInnerClassSingleton =
        new StaticInnerClassSingleton();
  }

  public static StaticInnerClassSingleton getInstance() {
    return InnerClass.staticInnerClassSingleton;
  }
}
```

## 饿汉式单例模式

饿汉式指的是，在类加载时就完成对单例的创建。

```java
package tech.selinux.design.pattern.creational.singleton;

public class HungrySingleton {

  // 声明为final的变量，必须在类加载时就完成赋值。
  private static final HungrySingleton hungrySingleton;

  static {
    hungrySingleton = new HungrySingleton();
  }

  public static HungrySingleton getInstance() {
    return hungrySingleton;
  }
}
```

## 枚举类型的单例模式

《Effective java》 中强烈推荐的一种线程安全的单例模式。

```java
package tech.selinux.design.pattern.creational.singleton;

public enum EnumInstance {
  INSTANCE {
    @Override
    protected void printTest() {
      System.out.println(" Test");
    }
  };

  protected abstract void printTest();

  private Object data;

  public Object getData() {
    return data;
  }

  public void setData(Object data) {
    this.data = data;
  }

  public static EnumInstance getInstance() {
    return INSTANCE;
  }
}
```

## 容器单例

基于容器的单例模式很好理解，就是使用一个数据结构俩管理我们创建的一些单例对象。这种方式非常适合，在程序初始化的时候，就准备好一些单例资源，然后来进行调用。

```java
package tech.selinux.design.pattern.creational.singleton;

import org.apache.commons.lang3.StringUtils;

import java.util.HashMap;
import java.util.Map;

public class ContainerSingleton {

  private ContainerSingleton() {}

  private static Map<String, Object> singletonMap = new HashMap<String, Object>();

  public static void putInstance(String key, Object instance) {
    if (StringUtils.isNotBlank(key) && instance != null) {
      if (!singletonMap.containsKey(key)) {
        singletonMap.put(key, instance);
      }
    }
  }

  public static Object getInstance(String key) {
    return singletonMap.get(key);
  }
}
```

但是有一点需要注意，hashmap本身并不是线程安全的，所以在使用的时候需要根据自己的实际应用场景来判断。虽然HashTable是线程安全的，但是每次都要加锁，会严重影响性能。所以实际使用过程中需要进行一个折中。

## 基于ThreadLocal的「单例」模式

这种模式的单例模式并不是全局唯一，但是在多线程运行环境中，却能够保证每个线程内部的单例唯一。

```java
package tech.selinux.design.pattern.creational.singleton;

/** 不能保证应用全局唯一，但是能够保证线程内部唯一。 */
public class ThreadLocalInstance {
  private static final ThreadLocal<ThreadLocalInstance> threadLocalInstanceThreadLocal =
      new ThreadLocal<ThreadLocalInstance>() {
        @Override
        protected ThreadLocalInstance initialValue() {
          return new ThreadLocalInstance();
        }
      };

  private ThreadLocalInstance() {}

  public static ThreadLocalInstance getInstance() {
    return threadLocalInstanceThreadLocal.get();
  }
}
```

### 补充另一个版本的Java/Scala Demo 以及源码解析

## Java Demo\_

## Scala Demo

## UML\_

## 源码解析


# 原型模式

   **原型模式(Prototype Pattern)**：使用原型实例指定创建对象的种类，并且通过拷贝这些原型创建新的对象。原型模式是一种对象创建型模式。 不需要知道任何创建的细节，不调用构造函数.

## 适用场景

* 类初始化消耗较多资源
* new 产生的一个对象需要非常繁琐的过程(数据准备、访问权限等)
* 构造函数比较复杂
* 循环体中生产大量的对象时

## 优点

* 比直接new一个对象性能高
* 简化创建过程

## 缺点

* 必须配备克隆方法
* 对克隆复杂对象或对克隆出的对象进行复杂改造时,容易引入风险
* 深拷贝、浅拷贝要运用得当

## Golang Demo

```go
package prototype

// 原型对象需要实现的接口
type Cloneable interface {
    Clone() Cloneable
}

type User struct {
    name string
    age  int
}

func (u User) Clone() Cloneable {
    return u
}
```

```go
package prototype

import (
    "fmt"
    "strconv"
    "testing"
)

func Test(t *testing.T) {

    user := User{}
    user.name = "init"
    usermap := make(map[string]User)
    for i := 1; i < 10; i++ {
        u := user.Clone().(User)
        u.name = strconv.Itoa(i)
        u.age = i
        usermap[strconv.Itoa(i)] = u
    }
    fmt.Println(user)
    fmt.Println(usermap)
}
```

由于golang中引入了指针，所以很巧的是，如果在返回值时，不指明是指针引用的话，就是值拷贝，因此原型模式的应用在golang中并不能得到很好的体现。也有可能是笔者学习不到位，后期会进行补充。

下面给出github上的一种实现方式。<https://github.com/senghoo/golang-design-pattern/blob/master/07_prototype/prototype.go>

```go
package prototype

//Cloneable 是原型对象需要实现的接口
type Cloneable interface {
    Clone() Cloneable
}

type PrototypeManager struct {
    prototypes map[string]Cloneable
}

func NewPrototypeManager() *PrototypeManager {
    return &PrototypeManager{
        prototypes: make(map[string]Cloneable),
    }
}

func (p *PrototypeManager) Get(name string) Cloneable {
    return p.prototypes[name]
}

func (p *PrototypeManager) Set(name string, prototype Cloneable) {
    p.prototypes[name] = prototype
}
```

```go
package prototype

import "testing"

var manager *PrototypeManager

type Type1 struct {
    name string
}

func (t *Type1) Clone() Cloneable {
    tc := *t
    return &tc
}

type Type2 struct {
    name string
}

func (t *Type2) Clone() Cloneable {
    tc := *t
    return &tc
}

func TestClone(t *testing.T) {
    t1 := manager.Get("t1")

    t2 := t1.Clone()

    if t1 == t2 {
        t.Fatal("error! get clone not working")
    }
}

func TestCloneFromManager(t *testing.T) {
    c := manager.Get("t1").Clone()

    t1 := c.(*Type1)
    if t1.name != "type1" {
        t.Fatal("error")
    }

}

func init() {
    manager = NewPrototypeManager()

    t1 := &Type1{
        name: "type1",
    }
    manager.Set("t1", t1)
}
```

## Java Demo

**浅拷贝**,clone 的时候，并不会调用构造器。浅克隆默认引用的是同一个对象，这样是会有隐患的。

```java
package tech.selinux.design.pattern.creational.prototype;

public class Mail implements Cloneable {
  private String name;
  private String emailAddress;
  private String content;

  public Mail() {
    System.out.println("Mail Class Constructor");
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getEmailAddress() {
    return emailAddress;
  }

  public void setEmailAddress(String emailAddress) {
    this.emailAddress = emailAddress;
  }

  public String getContent() {
    return content;
  }

  public void setContent(String content) {
    this.content = content;
  }

  @Override
  public String toString() {
    return "Mail{"
        + "name='"
        + name
        + '\''
        + ", emailAddress='"
        + emailAddress
        + '\''
        + ", content='"
        + content
        + '\''
        + '}'
        + super.toString();
  }

  @Override
  protected Object clone() throws CloneNotSupportedException {
    System.out.println("clone mail object");
    return super.clone();
  }
}
```

```java
package tech.selinux.design.pattern.creational.prototype;

import java.text.MessageFormat;

public class MailUtil {
  public static void sendMail(Mail mail) {
    String outputContent = "向{0}用户,邮件地址:{1},邮件内容:{2}发送邮件成功";
    System.out.println(
        MessageFormat.format(
            outputContent, mail.getName(), mail.getEmailAddress(), mail.getContent()));
  }

  public static void saveOriginMailRecord(Mail mail) {
    System.out.println("存储originMail记录,originMail:" + mail.getContent());
  }
}
```

```java
package tech.selinux.design.pattern.creational.prototype;

public class Test {
  public static void main(String[] args) throws CloneNotSupportedException {
    Mail mail = new Mail();
    mail.setContent("初始化模板");
    System.out.println("初始化mail:" + mail);
    for (int i = 0; i < 10; i++) {
      Mail mailTemp = (Mail) mail.clone();
      mailTemp.setName("姓名" + i);
      mailTemp.setEmailAddress("姓名" + i + "@selinux.tech");
      mailTemp.setContent("恭喜您，此次中奖了");
      MailUtil.sendMail(mailTemp);
      System.out.println("克隆的mailTemp:" + mailTemp);
    }
    MailUtil.saveOriginMailRecord(mail);
  }
}
```

**深克隆**，深克隆也需要对clone方法进行重写。对于引用类型一定要注意是否需要深克隆。

```java
package tech.selinux.design.pattern.creational.prototype.clone;

import java.util.Date;

public class User implements Cloneable {
  private String name;
  private Date birthday;

  public User(String name, Date birthday) {
    this.name = name;
    this.birthday = birthday;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public Date getBirthday() {
    return birthday;
  }

  public void setBirthday(Date birthday) {
    this.birthday = birthday;
  }

  @Override
  protected Object clone() throws CloneNotSupportedException {
    User user = (User) super.clone();
    // 深克隆
    user.birthday = (Date) user.birthday.clone();
    return user;
  }

  @Override
  public String toString() {
    return "User{" + "name='" + name + '\'' + ", birthday=" + birthday + '}' + super.toString();
  }
}
```

```java
package tech.selinux.design.pattern.creational.prototype.clone;

import java.util.Date;

public class Test {
  public static void main(String[] args) throws CloneNotSupportedException {
    Date birthday = new Date(0L);
    User user = new User("佩奇", birthday);
    User user1 = (User) user.clone();
    System.out.println(user);
    System.out.println(user1);

    user.getBirthday().setTime(666666666666L);

    System.out.println(user);
    System.out.println(user1);
  }
}
```

**通过抽象类的方式实现原型**,如果实际业务中能够进行合理的抽象的话，可以使用下面的方式。

```java
package tech.selinux.design.pattern.creational.prototype.abstractprototype;

public abstract class A implements Cloneable {
  @Override
  protected Object clone() throws CloneNotSupportedException {
    return super.clone();
  }
}
```

```java
package tech.selinux.design.pattern.creational.prototype.abstractprototype;

public class B extends A {
  public static void main(String[] args) throws CloneNotSupportedException {
    B b = new B();
    b.clone();
  }
}
```

### 补充另一个版本的Java/Scala Demo 以及源码解析

## Java Demo\_

## Scala Demo

## UML\_

## 源码解析


# 结构型模式


# 外观模式

   **外观模式(Facade Pattern)**：为子系统中的一组接口提供一个统一的入口。外观模式定义了一个高层接口，这个接口使得这一子系统更加容易使用。

## 适用场景

* 子系统越来越复杂，增加外观模式提供简单的接口调用。
* 构建多层系统,利用外观对象作为每层的入口，简化层间调用。

## 优点

* 简化调用过程，无需深入了解子系统，防止带来风险。
* 减少系统依赖，松散耦合。
* 更好的划分访问层次
* 符合迪米特法则，即最少知道原则

## 缺点

* 增加子系统、扩展子系统容易引入风险。
* 不符合开闭原则。

接下来，我们引入一个应用场景，然后结合代码来学习外观模式。 在我们实际生活中，例如使用信用卡，会获得相应的积分，每一定量的积分， 就可以在积分商城里面进行兑换。那这样一种应用场景里面，会进行如下的子系统区分。

* 积分资格校验子系统
* 支付子系统
* 物流子系统

## Golang Demo

```go
package facade

import "fmt"

type PonitsGift struct {
    name string
}

func NewPonitsGift(name string) *PonitsGift {
    return &PonitsGift{name: name}
}

type QualifyService struct {
}

func (QualifyService) isAvailable(gift PonitsGift) bool {
    fmt.Println("校验 " + gift.name + " 积分资格通过，库存通过")
    return true

}

type PointsPaymentService struct {
}

func (PointsPaymentService) pay(gift PonitsGift) bool {
    fmt.Println("积分支付 " + gift.name + " 成功")
    return true
}

type ShippingService struct {
}

func (ShippingService) shipGift(gift PonitsGift) (orderNo string) {
    fmt.Println(gift.name + " 派单成功，进入物流")
    return "666"
}
```

```go
package facade

import "fmt"

type GiftExchangeService struct {
    qualifyService       QualifyService
    pointsPaymentService PointsPaymentService
    shippingService      ShippingService
}

func (g GiftExchangeService) giftExchange(gift PonitsGift) {
    if g.qualifyService.isAvailable(gift) {
        // 资格校验通过
        if g.pointsPaymentService.pay(gift) {
            // 如果支付积分成功
            shippingOrderNo := g.shippingService.shipGift(gift)
            fmt.Println("物流系统下单成功,订单号是:" + shippingOrderNo)
        }
    }
}
```

```go
package facade

import "testing"

func Test(t *testing.T) {

    giftExchangeService := GiftExchangeService{}

    pointsGift := NewPonitsGift("耳机")

    giftExchangeService.giftExchange(*pointsGift)
}
```

## Java Demo

定义积分礼物

```java
package tech.selinux.design.pattern.structural.facade;

public class PointsGift {
  private String name;

  public PointsGift(String name) {
    this.name = name;
  }

  public String getName() {
    return name;
  }
}
```

积分资格校验

```java
package tech.selinux.design.pattern.structural.facade;

public class QualifyService {
  public boolean isAvailable(PointsGift pointsGift) {
    System.out.println("校验" + pointsGift.getName() + " 积分资格通过,库存通过");
    return true;
  }
}
```

积分扣减系统，支付系统

```java
package tech.selinux.design.pattern.structural.facade;

public class PointsPaymentService {
  public boolean pay(PointsGift pointsGift) {
    // 扣减积分
    System.out.println("支付" + pointsGift.getName() + " 积分成功");
    return true;
  }
}
```

物流服务子系统

```java
package tech.selinux.design.pattern.structural.facade;

public class ShippingService {
  public String shipGift(PointsGift pointsGift) {
    // 物流系统的对接逻辑
    System.out.println(pointsGift.getName() + "进入物流系统");
    String shippingOrderNo = "666";
    return shippingOrderNo;
  }
}
```

应用层不关心，子系统，应用层只和外观类进行通信。这里要注意。

```java
package tech.selinux.design.pattern.structural.facade;

public class GiftExchangeService {
  // 外观类在创建时，其中所依赖的子系统 service 就已经被创建好了
  // 因为外部调用时，不需要关心子系统
  private QualifyService qualifyService = new QualifyService();
  private PointsPaymentService pointsPaymentService = new PointsPaymentService();
  private ShippingService shippingService = new ShippingService();

  public void giftExchange(PointsGift pointsGift) {
    if (qualifyService.isAvailable(pointsGift)) {
      // 资格校验通过
      if (pointsPaymentService.pay(pointsGift)) {
        // 如果支付积分成功
        String shippingOrderNo = shippingService.shipGift(pointsGift);
        System.out.println("物流系统下单成功,订单号是:" + shippingOrderNo);
      }
    }
  }
}
```

```java
package tech.selinux.design.pattern.structural.facade;

public class Test {
  public static void main(String[] args) {
    PointsGift pointsGift = new PointsGift("T恤");
    GiftExchangeService giftExchangeService = new GiftExchangeService();
    giftExchangeService.giftExchange(pointsGift);
  }
}
```

## UML

![外观模式UML](/files/-LdCGXA_0WA-b-cYyNLk)

### 补充另一个版本的Java/Scala Demo 以及源码解析

## Java Demo\_

## Scala Demo

## UML\_

## 源码解析


# 装饰模式

   **装饰模式(Decorator Pattern)**：动态地给一个对象增加一些额外的职责，就增加对象功能来说，装饰模式比生成子类实现更为灵活。装饰模式是一种对象结构型模式。提供了比继承更有弹性的替代方案(扩展原有对象的功能)

## 适用场景

* 扩展一个类的功能或给一个类添加职责
* 动态的给一个对象添加功能,这些功能可以再动态地撤销

## 优点

* 继承的有力补充,比继承灵活,不改变原有对象的情况下给一个对象扩展功能。
* 通过使用不同的装饰类,以及这些装饰类的排列组合,可以实现不同的效果。
* 符合开闭原则

## 缺点

* 会出现更多的代码，更多类，增加程序复杂性
* 动态装饰时,多层装饰时会更复杂

## 装饰者相关模式

* 装饰者模式和代理模式
* 装饰者模式和适配器模式

## Golang Demo

```go
package decorator

type ICake interface {
    desc() string
    cost() int
}

type Cake struct {
}

func (Cake) desc() string {
    return "蛋糕"
}

func (Cake) cost() int {
    return 8
}

type AppleDecorator struct {
    icake ICake
}

func AddAppelDecorator(cake ICake) ICake {
    return &AppleDecorator{
        icake: cake,
    }
}

func (a AppleDecorator) desc() string {
    return a.icake.desc() + "add apple "
}

func (a AppleDecorator) cost() int {
    return a.icake.cost() + 2
}

type OrangeDecorator struct {
    icake ICake
}

func AddOrangeDecorator(cake ICake) ICake {
    return &OrangeDecorator{
        icake: cake,
    }

}

func (o OrangeDecorator) desc() string {
    return o.icake.desc() + "add orange "
}

func (o OrangeDecorator) cost() int {
    return o.icake.cost() + 1
}
```

```go
package decorator

import (
    "fmt"
    "testing"
)

func Test(t *testing.T) {
    var cake ICake = &Cake{}

    cake = AddOrangeDecorator(cake)
    cake = AddAppelDecorator(cake)

    fmt.Println(cake.desc())
    fmt.Println(cake.cost())
}
```

## Java Demo

```java
package tech.selinux.design.pattern.structural.decorator.v2;

public abstract class Acake {
  protected abstract String getDesc();

  protected abstract int cost();
}
```

```java
package tech.selinux.design.pattern.structural.decorator.v2;

public abstract class AbstractDecorator extends Acake {
  private Acake acake;

  public AbstractDecorator(Acake acake) {
    this.acake = acake;
  }

  protected abstract void doSomething();

  @Override
  protected String getDesc() {
    return this.acake.getDesc();
  }

  @Override
  protected int cost() {
    return this.acake.cost();
  }
}
```

```java
package tech.selinux.design.pattern.structural.decorator.v2;

public class Cake extends Acake {
  @Override
  protected String getDesc() {
    return "蛋糕";
  }

  @Override
  protected int cost() {
    return 8;
  }
}
```

```java
package tech.selinux.design.pattern.structural.decorator.v2;

public class AppleDecorator extends AbstractDecorator {
  public AppleDecorator(Acake acake) {
    super(acake);
  }

  @Override
  protected void doSomething() {}

  @Override
  protected String getDesc() {
    return super.getDesc() + " add apple";
  }

  @Override
  protected int cost() {
    return super.cost() + 1;
  }
}
```

```java
package tech.selinux.design.pattern.structural.decorator.v2;

public class OrangeDecorator extends AbstractDecorator {
  public OrangeDecorator(Acake acake) {
    super(acake);
  }

  @Override
  protected void doSomething() {}

  @Override
  protected String getDesc() {
    return super.getDesc() + " add orange";
  }

  @Override
  protected int cost() {
    return super.cost() + 2;
  }
}
```

```java
package tech.selinux.design.pattern.structural.decorator.v2;

public class Test {
  public static void main(String[] args) {
    Acake acake;
    acake = new Cake();
    acake = new AppleDecorator(acake);
    acake = new AppleDecorator(acake);
    acake = new OrangeDecorator(acake);

    System.out.println(acake.getDesc() + " 销售价格:" + acake.cost());
  }
}
```

## UML

![装饰者模式UML](/files/-Ld1pMnib3HzvB36yw_j)

### 补充另一个版本的Java/Scala Demo 以及源码解析

## Java Demo\_

## Scala Demo

## UML\_

## 源码解析


# 适配器模式

   **适配器模式(Adapter Pattern)**：将一个接口(被适配者)转换成客户希望的另一个接口(目标)，使接口不兼容的那些类可以一起工作，其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式，也可以作为对象结构型模式。

**【注：在适配器模式定义中所提及的接口是指广义的接口，它可以表示一个方法或者方法的集合。】**

## 适用场景

* 已经存在的类,它的方法和需求不匹配时(方法结果相同或相似)
* 不是软件设计阶段考虑的设计模式，是随着软件维护，由于不同产品，不同厂家造成功能类似而接口不相同情况下的解决方案。

## 优点

* 提高类的透明性和复用，现有的类复用但不需要改变
* 目标类和和适配器类解耦,提高程序扩展性
* 符合开闭原则

## 缺点

* 适配器在编写过程中需要全面考虑，可能会增加系统的复杂性
* 增加系统代码可读的难度

## Golang Demo

### 类适配器模式(go)

```go
package classadapter

import "fmt"

type Adaptee struct {
}

func (Adaptee) adapteeRequest() {
    fmt.Println("被适配者的方法")
}
```

```go
package classadapter

import "fmt"

type Target interface {
    request()
}

type ConcreteTarget struct {
}

func (ConcreteTarget) request() {
    fmt.Println("concreteTarget目标方法")
}
```

```go
package classadapter

type Adapter struct {
    Adaptee
}

func (a Adapter) request() {
    a.adapteeRequest()
}
```

```go
package classadapter

import "testing"

func Test(t *testing.T) {
    var target Target = ConcreteTarget{}
    target.request()

    var adapterTarget Target = Adapter{}
    adapterTarget.request()
}
```

### 对象适配器模式(go)

```go
package objectadapter

import "fmt"

type Adaptee struct {
}

func (Adaptee) adapteeRequest() {
    fmt.Println("被适配者的方法")
}
```

```go
package objectadapter

import "fmt"

type Target interface {
    request()
}

type ConcreteTarget struct {
}

func (ConcreteTarget) request() {
    fmt.Println("concreteTarget目标方法")
}
```

```go
package objectadapter

type Adapter struct {
    adaptee Adaptee
}

func (a Adapter) request() {
    a.adaptee.adapteeRequest()
}
```

```go
package objectadapter

import "testing"

func Test(t *testing.T) {
    var target Target = ConcreteTarget{}
    target.request()

    var adapterTarget Target = Adapter{}
    adapterTarget.request()
}
```

## Java Demo

### 类适配器模式

类适配器强调的是继承，通过继承实现目标target的方法。

```java
package tech.selinux.design.pattern.structural.adapter.classadapter;

public class Adaptee {
  public void adapteeRequest() {
    System.out.println("被适配者的方法");
  }
}
```

```java
package tech.selinux.design.pattern.structural.adapter.classadapter;

public interface Target {
  void request();
}
```

```java
package tech.selinux.design.pattern.structural.adapter.classadapter;

public class ConcreteTarget implements Target {
  @Override
  public void request() {
    System.out.println("concreteTarget目标方法");
  }
}
```

```java
package tech.selinux.design.pattern.structural.adapter.classadapter;

public class Adapter extends Adaptee implements Target {
  @Override
  public void request() {
    // ...
    super.adapteeRequest();
    // ...
  }
}
```

```java
package tech.selinux.design.pattern.structural.adapter.classadapter;

public class Test {
  public static void main(String[] args) {
    Target target = new ConcreteTarget();
    target.request();

    Target adapterTarget = new Adapter();
    adapterTarget.request();
  }
}
```

#### 类适配器UML

![类适配器UML](/files/-Ld23pCkB-N5xfZdHT2O)

### 对象适配器模式

对象适配器模式，是通过组合的形式来完成适配，在实际开发中，如果能够适用组合的形式，就尽量不用要继承。

```java
package tech.selinux.design.pattern.structural.adapter.objectadapter;

public class Adaptee {
  public void adapteeRequest() {
    System.out.println("被适配者的方法");
  }
}
```

```java
package tech.selinux.design.pattern.structural.adapter.objectadapter;

public interface Target {
  void request();
}
```

```java
package tech.selinux.design.pattern.structural.adapter.objectadapter;

public class ConcreteTarget implements Target {
  @Override
  public void request() {
    System.out.println("concreteTarget目标方法");
  }
}
```

```java
package tech.selinux.design.pattern.structural.adapter.objectadapter;

public class Adapter implements Target {
  private Adaptee adaptee = new Adaptee();

  @Override
  public void request() {
    // ...
    adaptee.adapteeRequest();
    // ...
  }
}
```

```java
package tech.selinux.design.pattern.structural.adapter.objectadapter;

public class Test {
  public static void main(String[] args) {
    Target target = new ConcreteTarget();
    target.request();

    Target adapterTarget = new Adapter();
    adapterTarget.request();
  }
}
```

## UML

![对象适配器模式UML](/files/-Ld23pCmSqDjZL_mdNhn)

### 补充另一个版本的Java/Scala Demo 以及源码解析

## Java Demo\_

## Scala Demo

## UML\_

## 源码解析


# 享元模式

   **享元模式(Flyweight Pattern)**:运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象，而这些对象都很相似，状态变化很小，可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象，因此它又称为轻量级模式，它是一种对象结构型模式。**提供了减少对象数量从而改善应用所需的对象结构的的方式**

## 适用场景

* 尝尝应用于系统的底层开发，以便解决系统的性能问题。例如数据库的连接池。
* 系统有大量的相似对象，需要缓冲池的场景。

## 优点

* 减少对象的创建，降低内存中对象的数量，降低系统的内存，提高效率
* 减少内存之外的其他资源

## 缺点

* 关注内/外部状态、关注线程安全问题
* 使系统、程序逻辑复杂化

## 扩展

* 内部状态，不随着环境的改变而改变，位于享元对象的内部。
* 外部状态，因为环境的改变而改变，一般位于享元模式的外部。

通常可以理解，内部状态就是享元对象的属性。

## Golang Demo

```go
package flyweight

import (
    "fmt"
)

type Employee interface {
    report()
}

type Manager struct {
    title         string
    department    string
    reportContent string
}

func (m Manager) report() {
    fmt.Println(m.reportContent)
}

func NewManager(department string) Manager {
    return Manager{
        title:      "部门经理",
        department: department,
    }
}

func (m *Manager) SetReportContent(reportContent string) {
    m.reportContent = reportContent
}

type EmployeeFactory struct {
    employeeMap map[string]Employee
}

func NewEmployeeFactory() *EmployeeFactory {
    return &EmployeeFactory{
        employeeMap: make(map[string]Employee),
    }
}

func (e *EmployeeFactory) getManager(department string) Employee {

    var manager Manager
    if v, ok := e.employeeMap[department]; !ok {
        manager = NewManager(department)
        fmt.Println("创建部门经理:" + department)
    } else {
        manager = v.(Manager)
    }
    reportContent := department + "部门汇报:此次报告的主要内容是......"
    manager.SetReportContent(reportContent)
    fmt.Println(" 创建报告:" + reportContent)
    e.employeeMap[department] = manager
    return manager
}
```

```go
package flyweight

import (
    "math/rand"
    "testing"
    "time"
)

func Test(t *testing.T) {
    departments := []string{"RD", "QA", "PM", "BD"}
    rand.Seed(time.Now().UnixNano())
    employeeFactory := NewEmployeeFactory()
    for i := 0; i < 10; i++ {
        department := departments[int(rand.Float32()*4)]
        manager := employeeFactory.getManager(department).(Manager)
        manager.report()
    }
}
```

## Java Demo

```java
package tech.selinux.design.pattern.structural.flyweight;

public interface Employee {
  void report();
}
```

```java
package tech.selinux.design.pattern.structural.flyweight;

public class Manager implements Employee {
  @Override
  public void report() {
    System.out.println(reportContent);
  }

  private String title = "部门经理";
  private String department;
  private String reportContent;

  public void setReportContent(String reportContent) {
    this.reportContent = reportContent;
  }

  public Manager(String department) {
    this.department = department;
  }
}
```

```java
package tech.selinux.design.pattern.structural.flyweight;

import java.util.HashMap;
import java.util.Map;

public class EmployeeFactory {
  private static final Map<String, Employee> EMPLOYEE_MAP = new HashMap<String, Employee>();

  public static Employee getManager(String department) {
    Manager manager = (Manager) EMPLOYEE_MAP.get(department);

    if (manager == null) {
      manager = new Manager(department);
      System.out.print("创建部门经理:" + department);
      String reportContent = department + "部门汇报:此次报告的主要内容是......";
      manager.setReportContent(reportContent);
      System.out.println(" 创建报告:" + reportContent);
      EMPLOYEE_MAP.put(department, manager);
    }
    return manager;
  }
}
```

```java
package tech.selinux.design.pattern.structural.flyweight;

public class Test {
  private static final String departments[] = {"RD", "QA", "PM", "BD"};

  public static void main(String[] args) {
    for (int i = 0; i < 10; i++) {
      String department = departments[(int) (Math.random() * departments.length)];
      Manager manager = (Manager) EmployeeFactory.getManager(department);
      manager.report();
    }
  }
}
```

这里面，title 一直不会发生变化，就是内部状态，而department需要通过setter方法来使用。就可以理解为title是内部状态，而department使外部状态。

## UML

![享元模式UML](/files/-LdEig3ISF2d35o13X8d)

### 补充另一个版本的Java/Scala Demo 以及源码解析

## Java Demo\_

## Scala Demo

## UML\_

## 源码解析


# 组合模式

   **组合模式(Composite Pattern)**：组合多个对象形成树形结构以表示具有“整体—部分”关系的层次结构。组合模式对单个对象（即叶子对象）和组合对象（即容器对象）的使用具有一致性，组合模式又可以称为“整体—部分”(Part-Whole)模式，它是一种对象结构型模式。

## 适用场景

* 希望客户端可以忽略组合对象与单个对象的差异时
* 处理一个树形结构

## 优点

* 清楚地定义分层次的复杂对象，表示对象的全部或部分层次
* 让客户端忽略了层次的差异，方便对整个层次结构进行控制
* 简化客户端代码
* 符合开闭原则

## 缺点

* 限制类型时比较复杂
* 使设计变得更加抽象

下面我们引出一个应用场景，假设，我们有一个课程目录，如何使用组合模式在实现这个目录呢？

## Golang Demo

```go
package composite

type CatalogComponent interface {
    add(catalogComponent CatalogComponent)
    remove(catalogComponent CatalogComponent)
    Name(catalogComponent CatalogComponent) string
    Price(catalogComponent CatalogComponent) float32
    print()
}
```

```go
package composite

import (
    "fmt"
)

type Course struct {
    name  string
    price float32
}

func (c *Course) add(catalogComponent CatalogComponent) {
    panic("implement me")
}

func (c *Course) remove(catalogComponent CatalogComponent) {
    panic("implement me")
}

func NewCourse(name string, price float32) *Course {
    return &Course{name: name, price: price}
}
func (c *Course) Name(catalogComponent CatalogComponent) string {
    return c.name
}

func (c *Course) Price(catalogComponent CatalogComponent) float32 {
    return c.price
}

func (c *Course) print() {

    fmt.Printf("Course Name:%v, Price:%v \n", c.name, c.price)
}
```

```go
package composite

import (
    "fmt"
)

type CourseCatalog struct {
    items []CatalogComponent
    name  string
    level int
}

func (c *CourseCatalog) remove(catalogComponent CatalogComponent) {
    panic("implement me")
}

func (c *CourseCatalog) Price(catalogComponent CatalogComponent) float32 {
    panic("implement me")
}

func NewCourseCatalog(name string, level int) *CourseCatalog {
    return &CourseCatalog{
        items: []CatalogComponent{},
        name:  name,
        level: level,
    }

}

func (c *CourseCatalog) add(catalogComponent CatalogComponent) {

    c.items = append(c.items, catalogComponent)
}

func (c *CourseCatalog) Name(catalogComponent CatalogComponent) string {
    return c.name
}

func (c *CourseCatalog) print() {
    fmt.Println(c.name)
    for _, catalogComponent := range c.items {
        if c.level != 0 {
            for i := 0; i < c.level; i++ {
                fmt.Print("  ")
            }
        }
        catalogComponent.print()
    }

}
```

```go
package composite

import (
    "testing"
)

func Test(t *testing.T) {

    var linuxCourse CatalogComponent = NewCourse("Linux Course", 11)
    var windowsCourse CatalogComponent = NewCourse("Windows Course", 11)

    var javaCourseCatalog CatalogComponent = NewCourseCatalog("Java Course catalog", 2)

    var javaCourse1 CatalogComponent = NewCourse("Java Course Ⅰ", 55)
    var javaCourse2 CatalogComponent = NewCourse("Java Course Ⅱ", 66)
    var designPattern CatalogComponent = NewCourse("Java design pattern", 77)

    javaCourseCatalog.add(javaCourse1)
    javaCourseCatalog.add(javaCourse2)
    javaCourseCatalog.add(designPattern)

    mainCourseCatalog := NewCourseCatalog("Course catalog", 1)
    mainCourseCatalog.add(linuxCourse)
    mainCourseCatalog.add(windowsCourse)
    mainCourseCatalog.add(javaCourseCatalog)

    mainCourseCatalog.print()
}
```

## Java Demo

```java
package tech.selinux.design.pattern.structural.composite;

/** 定义一个抽象类， 子类需要将所有的操作进行重写 */
public abstract class CatalogComponent {

  public void add(CatalogComponent catalogComponent) {
    throw new UnsupportedOperationException("不支持添加操作");
  }

  public void remove(CatalogComponent catalogComponent) {
    throw new UnsupportedOperationException("不支持删除操作");
  }

  public String getName(CatalogComponent catalogComponent) {
    throw new UnsupportedOperationException("不支持获取名称操作");
  }

  public double getPrice(CatalogComponent catalogComponent) {
    throw new UnsupportedOperationException("不支持获取价格操作");
  }

  public void print() {
    throw new UnsupportedOperationException("不支持打印操作");
  }
}
```

```java
package tech.selinux.design.pattern.structural.composite;

public class Course extends CatalogComponent {
  private String name;
  // 目录层级
  private double price;

  public Course(String name, double price) {
    this.name = name;
    this.price = price;
  }

  @Override
  public String getName(CatalogComponent catalogComponent) {
    return this.name;
  }

  @Override
  public double getPrice(CatalogComponent catalogComponent) {
    return this.price;
  }

  @Override
  public void print() {
    System.out.println("Course Name:" + name + " Price:" + price);
  }
}
```

```java
package tech.selinux.design.pattern.structural.composite;

import java.util.ArrayList;
import java.util.List;

public class CourseCatalog extends CatalogComponent {
  private List<CatalogComponent> items = new ArrayList<CatalogComponent>();
  private String name;
  private Integer level;

  public CourseCatalog(String name, Integer level) {
    this.name = name;
    this.level = level;
  }

  @Override
  public void add(CatalogComponent catalogComponent) {
    items.add(catalogComponent);
  }

  @Override
  public String getName(CatalogComponent catalogComponent) {
    return this.name;
  }

  @Override
  public void remove(CatalogComponent catalogComponent) {
    items.remove(catalogComponent);
  }

  @Override
  public void print() {
    System.out.println(this.name);
    for (CatalogComponent catalogComponent : items) {
      if (this.level != null) {
        for (int i = 0; i < this.level; i++) {
          System.out.print("  ");
        }
      }
      catalogComponent.print();
    }
  }
}
```

```java
package tech.selinux.design.pattern.structural.composite;

public class Test {
  public static void main(String[] args) {
    CatalogComponent linuxCourse = new Course("Linux Course", 11);
    CatalogComponent windowsCourse = new Course("Windows Course", 11);

    CatalogComponent javaCourseCatalog = new CourseCatalog("Java Course catalog", 2);

    CatalogComponent javaCourse1 = new Course("Java Course Ⅰ", 55);
    CatalogComponent javaCourse2 = new Course("Java Course Ⅱ", 66);
    CatalogComponent designPattern = new Course("Java design pattern", 77);

    javaCourseCatalog.add(javaCourse1);
    javaCourseCatalog.add(javaCourse2);
    javaCourseCatalog.add(designPattern);

    CatalogComponent mainCourseCatalog = new CourseCatalog("Course catalog", 1);
    mainCourseCatalog.add(linuxCourse);
    mainCourseCatalog.add(windowsCourse);
    mainCourseCatalog.add(javaCourseCatalog);

    mainCourseCatalog.print();
  }
}
```

### 补充另一个版本的Java/Scala Demo 以及源码解析

## Java Demo\_

## Scala Demo

## UML\_

## 源码解析


# 桥接模式

   **桥接模式(Bridge Pattern)：** 将抽象部分与它的具体实现部分分离，使它们都可以独立地变化。它是一种对象结构型模式，又称为柄体(Handle and Body)模式或接口(Interface)模式。通过组合的方式实现两个类之间联系，而不是继承。

## 适用场景

* 抽象和具体实现之间增加更多的灵活性
* 一个类存在两个（或者多个）独立变化的维度，并且这两个（或者多个）维度都需要独立进行扩展
* 不希望使用继承，或者因为多层继承导致系统类的个数剧增

## 优点

* 分离抽象部分及其具体实现部分
* 提高了系统的可扩展性
* 符合开闭原则
* 符合合成复用原则

## 缺点

* 增加了系统的理解与设计难度
* 需要正确地识别出系统中两个独立变化的维度

下面我们引入一种应用场景，银行有很多种，每种银行又可以又多种储蓄账户，例如活期和定期。

## Golang Demo

```go
package bridge

// 账户
type Account interface {
    openAccount() Account
    showAccountType()
}
```

```go
package bridge

type Bank interface {
    openAccount() Account
}
```

```go
package bridge

import "fmt"

type DepositAccount struct {
}

func NewDepositAccount() *DepositAccount {
    return &DepositAccount{}
}

func (DepositAccount) openAccount() Account {
    fmt.Println("打开定期账号")
    return *NewDepositAccount()
}

func (DepositAccount) showAccountType() {
    fmt.Println("这是一个定期账号")
}
```

```go
package bridge

import "fmt"

type SavingAccount struct {
}

func NewSavingAccount() *SavingAccount {
    return &SavingAccount{}
}

func (SavingAccount) openAccount() Account {
    fmt.Println("打开活期账号")
    return *NewSavingAccount()
}

func (SavingAccount) showAccountType() {
    fmt.Println("这是一个活期账号")
}
```

```go
package bridge

import "fmt"

type ABCBank struct {
    account Account
}

func NewABCBank(account Account) *ABCBank {

    return &ABCBank{account: account}
}

func (abc ABCBank) openAccount() Account {
    fmt.Println("打开中国农业银行账号")
    abc.account.openAccount()
    return abc.account
}
```

```go
package bridge

import "fmt"

type ICBCBank struct {
    account Account
}

func NewICBCBank(account Account) *ICBCBank {

    return &ICBCBank{account: account}
}

func (icbc ICBCBank) openAccount() Account {

    fmt.Println("打开中国工商银行账号")
    icbc.account.openAccount()
    return icbc.account
}
```

```go
package bridge

import "testing"

func Test(t *testing.T) {
    var icbcBank Bank = NewICBCBank(NewDepositAccount())
    icbcAccount := icbcBank.openAccount()
    icbcAccount.showAccountType()

    var abcBank Bank = NewABCBank(NewSavingAccount())
    abcAccount := abcBank.openAccount()
    abcAccount.showAccountType()
}
```

## Java Demo

定义一个接口，里面定义了两种账户。

```java
package tech.selinux.design.pattern.structural.bridge;

public interface Account {
  Account openAccount();

  void showAccountType();
}
```

定义一个抽象类的银行，里面有一个账户，同时有一个与账户同名的方法。

```java
package tech.selinux.design.pattern.structural.bridge;

public abstract class Bank {
  protected Account account;

  public Bank(Account account) {
    this.account = account;
  }

  abstract Account openAccount();
}
```

分别定义两种账户的实现类。

```java
package tech.selinux.design.pattern.structural.bridge;

public class DepositAccount implements Account {
  @Override
  public Account openAccount() {
    System.out.println("打开定期账号");
    return new DepositAccount();
  }

  @Override
  public void showAccountType() {
    System.out.println("这是一个定期账号");
  }
}
```

```java
package tech.selinux.design.pattern.structural.bridge;

public class SavingAccount implements Account {
  @Override
  public Account openAccount() {
    System.out.println("打开活期账号");
    // ...
    return new SavingAccount();
  }

  @Override
  public void showAccountType() {
    System.out.println("这是一个活期账号");
  }
}
```

接下来定义两种银行的实现类。

```java
package tech.selinux.design.pattern.structural.bridge;

public class ABCBank extends Bank {
  public ABCBank(Account account) {
    super(account);
  }

  @Override
  Account openAccount() {
    System.out.println("打开中国农业银行账号");
    account.openAccount();
    return account;
  }
}
```

```java
package tech.selinux.design.pattern.structural.bridge;

public class ICBCBank extends Bank {
  public ICBCBank(Account account) {
    super(account);
  }

  @Override
  Account openAccount() {
    System.out.println("打开中国工商银行账号");
    account.openAccount();
    return account;
  }
}
```

然后我们来进行一下测试。

```java
package tech.selinux.design.pattern.structural.bridge;

public class Test {
  public static void main(String[] args) {
    Bank icbcBank = new ICBCBank(new DepositAccount());
    Account icbcAccount = icbcBank.openAccount();
    icbcAccount.showAccountType();

    Bank icbcBank2 = new ICBCBank(new SavingAccount());
    Account icbcAccount2 = icbcBank2.openAccount();
    icbcAccount2.showAccountType();

    Bank abcBank = new ABCBank(new SavingAccount());
    Account abcAccount = abcBank.openAccount();
    abcAccount.showAccountType();
  }
}
```

## UML

![桥接模式UML](/files/-LdObIL6cgEDE-x5fTsB)

### 补充另一个版本的Java/Scala Demo 以及源码解析

## Java Demo\_

## Scala Demo

## UML\_

## 源码解析


# 代理模式

   **代理模式(Proxy Pattern)**： 给某一个对象提供一个代理或占位符，并由代理对象来控制对原对象的访问。 代理对象在客户端和目标对象之间起到中介的作用。

## 适用场景

* 保护目标对象
* 增强目标对象

## 优点

* 代理模式能将代理对象与真实被调用的目标对象分离.
* 一定程度上降低了系统的耦合度，扩展性好。
* 保护目标对象
* 增强目标对象

## 缺点

* 代理模式会造成系统设计种类的数据增加
* 在客户端和目标对象增加一个代理对象，会造成请求处理速度变慢
* 增加系统的复杂度

## 扩展

* 静态代理
* 动态代理
* CGLib代理

下面我们来看一种业务场景，在买房的时候，我们经常会通过中介来进行沟通。中介

## Golang Demo

```go
package proxy

import "fmt"

type House interface {
    Sail()
}

type SmailHouse struct {
}

func (SmailHouse) Sail() {
    fmt.Println("100 万")
}

type Proxy struct {
    house SmailHouse
}

func NewProxy() *Proxy {
    return &Proxy{}
}

func (p Proxy) Sail() {

    var result string = "签订成功"
    p.Before()

    p.house.Sail()

    p.After()

    fmt.Println(result)
}

func (Proxy) Before() {

    fmt.Println("代理之前的一些检查")

}

func (Proxy) After() {

    fmt.Println("代理之后的一些检查")

}
```

```go
package proxy

import "testing"

func Test(t *testing.T) {

    var house House = NewProxy()
    house.Sail()

}
```

## Java Demo

接下来，我们引入一个业务场景。 进行商城下订单的设计，同时通过代理来进行分库。也就是我们会根据orderid的取模值来进行模式分库

首先我们模拟一下的spring编程模式。

定义一个订单类。

```java
package tech.selinux.design.pattern.structural.proxy;

public class Order {
  private Object orderInfo;
  private Integer userId;

  public Object getOrderInfo() {
    return orderInfo;
  }

  public void setOrderInfo(Object orderInfo) {
    this.orderInfo = orderInfo;
  }

  public Integer getUserId() {
    return userId;
  }

  public void setUserId(Integer userId) {
    this.userId = userId;
  }
}
```

接下来分别定义Dao层和Service层。

```java
package tech.selinux.design.pattern.structural.proxy;

public interface IOrderDao {
  int insert(Order order);
}
```

```java
package tech.selinux.design.pattern.structural.proxy;

public interface IOrderService {
  int saveOrder(Order order);
}
```

```java
package tech.selinux.design.pattern.structural.proxy;

public class OrderDaoImpl implements IOrderDao {
  @Override
  public int insert(Order order) {
    System.out.println("Dao层添加Order成功");
    return 1;
  }
}
```

```java
package tech.selinux.design.pattern.structural.proxy;

public class OrderServiceImpl implements IOrderService {
  private IOrderDao iOrderDao;

  @Override
  public int saveOrder(Order order) {
    iOrderDao = new OrderDaoImpl();
    System.out.println("Service层调用Dao层添加Order");
    return iOrderDao.insert(order);
  }
}
```

### 静态代理

```java
package tech.selinux.design.pattern.structural.proxy.staticproxy;

import tech.selinux.design.pattern.structural.proxy.IOrderService;
import tech.selinux.design.pattern.structural.proxy.Order;
import tech.selinux.design.pattern.structural.proxy.OrderServiceImpl;

public class OrderServiceStaticProxy {
  private IOrderService iOrderService;

  public int saveOrder(Order order) {
    beforeMethod(order);
    iOrderService = new OrderServiceImpl();
    int result = iOrderService.saveOrder(order);
    afterMethod();
    return result;
  }

  private void beforeMethod(Order order) {
    int userId = order.getUserId();
    int dbRouter = userId % 2;
    System.out.println("静态代理分配到【db" + dbRouter + "】处理数据");

    // todo 设置dataSource;
    System.out.println("db" + String.valueOf(dbRouter));
    System.out.println("静态代理 before code");
  }

  private void afterMethod() {
    System.out.println("静态代理 after code");
  }
}
```

```java
package tech.selinux.design.pattern.structural.proxy;

public class StaticTest {
  public static void main(String[] args) {
    Order order = new Order();
    order.setUserId(2);

    OrderServiceStaticProxy orderServiceStaticProxy = new OrderServiceStaticProxy();
    orderServiceStaticProxy.saveOrder(order);
  }
}
```

### 静态代理 UML

![静态代理 UML](/files/-LdOuJviM-2hSdTMVD71)

### 动态代理

```java
package tech.selinux.design.pattern.structural.proxy.dynamicproxy;

import tech.selinux.design.pattern.structural.proxy.Order;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class OrderServiceDynamicProxy implements InvocationHandler {

  private Object target;

  public OrderServiceDynamicProxy(Object target) {
    this.target = target;
  }

  public Object bind() {
    Class cls = target.getClass();
    return Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), this);
  }

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    Object argObject = args[0];
    beforeMethod(argObject);
    Object object = method.invoke(target, args);
    afterMethod();
    return object;
  }

  private void beforeMethod(Object obj) {
    int userId = 0;
    System.out.println("动态代理 before code");
    if (obj instanceof Order) {
      Order order = (Order) obj;
      userId = order.getUserId();
    }
    int dbRouter = userId % 2;
    System.out.println("动态代理分配到【db" + dbRouter + "】处理数据");

    // todo 设置dataSource;
    System.out.println("db" + String.valueOf(dbRouter));
  }

  private void afterMethod() {
    System.out.println("动态代理 after code");
  }
}
```

```java
package tech.selinux.design.pattern.structural.proxy;

public class DynamicTest {
  public static void main(String[] args) {
    Order order = new Order();
    order.setUserId(1);
    IOrderService orderServiceDynamicProxy =
        (IOrderService) new OrderServiceDynamicProxy(new OrderServiceImpl()).bind();

    orderServiceDynamicProxy.saveOrder(order);
  }
}
```

### 动态代理 UML

![动态代理的UML](/files/-LdOuJvkuKo0qWLQYrtv)

### 补充另一个版本的Java/Scala Demo 以及源码解析

## Java Demo\_

## Scala Demo

## UML\_

## 源码解析


# 行为型模式


# 模板方法模式

模板方法模式 (Template Method Pattern): 定义了一个算法的骨架，并允许子类为一个或多个步骤提供实现,模板方法模式使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

## 适用场景

* 一次性实现算法的不变部分，并将可变的行为留给子类来实现。
* 各子类中公共的行为被提取出来并集中到一个公共的父类中，从而避免代码重复。

## 优点

* 提高复用性,将相同部分的代码放在抽象的父类中
* 提高扩展性，将不同的代码放在不同的子类中
* 符合开闭原则

## 缺点

* 类数目增加
* 增加了系统实现的复杂度
* 继承关系自身缺点，如果父类添加新的抽象方法，所有子类都要修改一遍

我们引入一种应用场景。产品线上可以使用一种产品线生产不同系列的产品，每种产品同属一种产品线，但是具体的实现细节有所差别。

## Golang Demo

```go
package templatemethod

import "fmt"

type IProduct interface {
    makeName()
    makeUse()
    afterSales()
    needAfterSales() bool

    packageProduct()
}

// golang 中不能定义抽象类，所以，我们使用一个struct 和一个 interface 组合来实现
type Product struct {
    iProduct IProduct
}

func NewProduct() *Product {
    return &Product{}
}

func (p *Product) makeProduct() {
    p.iProduct.makeName()
    p.iProduct.makeUse()
    if p.iProduct.needAfterSales() {
        p.iProduct.afterSales()
    }
    p.iProduct.packageProduct()
}

func (*Product) makeName() {
    fmt.Println("产品命名")

}
func (*Product) makeUse() {
    fmt.Println("产品使用手册")

}
func (*Product) afterSales() {
    fmt.Println("售后")

}

func (*Product) needAfterSales() bool {
    return false
}

func (*Product) packageProduct() {

}
```

```go
package templatemethod

import "fmt"

type DesignPatternProduct struct {
    Product
}

func NewDesignPatternProduct() *DesignPatternProduct {
    return &DesignPatternProduct{}
}

func (DesignPatternProduct) packageProduct() {
    fmt.Println("提供DesignPatternPorduct")

}

func (DesignPatternProduct) needAfterSales() bool {
    return true
}

type OtherPorduct struct {
    Product
    needAfterSalesFlag bool
}

func NewOtherPorduct(needAfterSalesFlag bool) *OtherPorduct {
    return &OtherPorduct{needAfterSalesFlag: needAfterSalesFlag}
}

func (o *OtherPorduct) packageProduct() {
    fmt.Println("提供OtherPorduct")
    fmt.Println("提供OtherPorduct other")
}

func (o *OtherPorduct) needAfterSales() bool {
    return o.needAfterSalesFlag
}
```

```go
package templatemethod

import (
    "fmt"
    "testing"
)

func Test(t *testing.T) {

    fmt.Println("pattern start---")

    designPatternProduct := NewProduct()
    designPatternProduct.iProduct = NewDesignPatternProduct()
    designPatternProduct.makeProduct()
    fmt.Println("pattern end---")

    fmt.Println("start---")
    otherProduct := NewProduct()
    otherProduct.iProduct = NewOtherPorduct(true)
    otherProduct.makeProduct()
    fmt.Println("end---")

}
```

## Java Demo

```java
package tech.selinux.design.pattern.behavioral.templatemethod;

public abstract class APorduct {

  protected final void makeProduct() {
    this.makeName();
    this.makeUse();
    if (needAfterSales()) {
      this.afterSales();
    }
    this.packageProduct();
  }

  final void makeName() {
    System.out.println("产品命名");
  }

  final void makeUse() {
    System.out.println("产品使用手册");
  }

  final void afterSales() {
    System.out.println("售后");
  }
  // 钩子方法
  protected boolean needAfterSales() {
    return false;
  }

  abstract void packageProduct();
}
```

```java
package tech.selinux.design.pattern.behavioral.templatemethod;

public class DesignPatternPorduct extends APorduct {
  @Override
  void packageProduct() {
    System.out.println("提供DesignPatternPorduct");
  }

  @Override
  protected boolean needAfterSales() {
    return true;
  }
}
```

```java
package tech.selinux.design.pattern.behavioral.templatemethod;

public class OtherPorduct extends APorduct {
  private boolean needAfterSalesFlag = false;

  @Override
  void packageProduct() {
    System.out.println("提供OtherPorduct");
    System.out.println("提供OtherPorduct other");
  }

  public OtherPorduct(boolean needAfterSalesFlag) {
    this.needAfterSalesFlag = needAfterSalesFlag;
  }

  @Override
  protected boolean needAfterSales() {
    return this.needAfterSalesFlag;
  }
}
```

```java
package tech.selinux.design.pattern.behavioral.templatemethod;

public class Test {
  public static void main(String[] args) {
    System.out.println("pattern start---");
    APorduct designPatternProduct = new DesignPatternPorduct();
    designPatternProduct.makeProduct();
    System.out.println("pattern end---");

    System.out.println("start---");
    APorduct product = new OtherPorduct(false);
    product.makeProduct();
    System.out.println("end---");
  }
}
```

## UML

![模板方法模式UML](/files/-LdYUDsbJS-81_rQ1ZAL)

### 补充另一个版本的Java/Scala Demo 以及源码解析

## Java Demo\_

## Scala Demo

## UML\_

## 源码解析


# 迭代器模式

**迭代器模式(Iterator Pattern)**: 提供一种方法，顺序访问一个集合对象中的各个元素，而又不暴露该对象的内部表示。

## 适用场景

* 访问一个集合对象的内容，而无需暴露它的内部表示。
* 为遍历不同的集合结构提供了一个统一的接口。

## 优点

* 分离了集合对象的遍历行为

## 缺点

* 类的个数成对增加，新增一个集合类，就需要相对应地增加一个迭代器类

迭代器模式在实际使用过程中是经常使用的一种设计模式，但是在实际编程过程中，很少有我们自己去实现一个迭代器，因为编程语言一般就给我们提供了相应集合类的迭代器。

## Golang Demo

下面的示例并不能完全地表示迭代模式，建议参考java版示例。因为没有add和remove方法。实际使用中，我们并不需要亲自来实现，因为了解就可以。

```go
package iterator

type Student struct {
    name string
}

type Iters []Student

func (i Iters) Iterator() *Iterator {

    return &Iterator{
        data:  i,
        index: 0,
    }

}

type Iterator struct {
    data  Iters
    index int
}

func (i *Iterator) HasNext() bool {
    return i.index < len(i.data)
}

func (i *Iterator) Next() (v Student) {
    v = i.data[i.index]
    i.index++
    return v
}
```

```go
package iterator

import (
    "fmt"
    "testing"
)

func Test(t *testing.T) {
    iters := Iters{
        Student{name: "hello"},
        Student{name: "world"},
        Student{name: "!"},
    }

    for it := iters.Iterator(); it.HasNext(); {
        fmt.Println(it.Next())
    }
}
```

## Java Demo

```java
package tech.selinux.design.pattern.behavioral.iterator;

public class Student {
  private String name;

  public Student(String name) {
    this.name = name;
  }

  public String getName() {
    return name;
  }
}
```

```java
package tech.selinux.design.pattern.behavioral.iterator;

public interface StudentAggregate {

  void addStudent(Student student);

  void removeStudent(Student student);

  StudentIterator getStudentIterator();
}
```

```java
package tech.selinux.design.pattern.behavioral.iterator;

import java.util.ArrayList;
import java.util.List;

public class StudentAggregateImpl implements StudentAggregate {

  private List studentList;

  public StudentAggregateImpl() {
    this.studentList = new ArrayList();
  }

  @Override
  public void addStudent(Student student) {
    studentList.add(student);
  }

  @Override
  public void removeStudent(Student student) {
    studentList.remove(student);
  }

  @Override
  public StudentIterator getStudentIterator() {
    return new StudentIteratorImpl(studentList);
  }
}
```

```java
package tech.selinux.design.pattern.behavioral.iterator;

public interface StudentIterator {
  Student nextStudent();

  boolean isLastStudent();
}
```

```java
package tech.selinux.design.pattern.behavioral.iterator;

import java.util.List;

public class StudentIteratorImpl implements StudentIterator {

  private List studentList;
  private int position;
  private Student student;

  public StudentIteratorImpl(List studentList) {
    this.studentList = studentList;
  }

  @Override
  public Student nextStudent() {
    System.out.println("result ,位置是: " + position);
    student = (Student) studentList.get(position);
    position++;
    return student;
  }

  @Override
  public boolean isLastStudent() {
    if (position < studentList.size()) {
      return false;
    }
    return true;
  }
}
```

```java
package tech.selinux.design.pattern.behavioral.iterator;

public class Test {

  public static void main(String[] args) {
    Student student1 = new Student("Student 1");
    Student student2 = new Student("Student 2");
    Student student3 = new Student("Student 3");
    Student student4 = new Student("Student 4");
    Student student5 = new Student("Student 5");
    Student student6 = new Student("Student 6");

    StudentAggregate studentAggregate = new StudentAggregateImpl();

    studentAggregate.addStudent(student1);
    studentAggregate.addStudent(student2);
    studentAggregate.addStudent(student3);
    studentAggregate.addStudent(student4);
    studentAggregate.addStudent(student5);
    studentAggregate.addStudent(student6);

    System.out.println("-----Student list -----");
    printCourses(studentAggregate);

    studentAggregate.removeStudent(student4);
    studentAggregate.removeStudent(student5);

    System.out.println("-----Student list 2-----");
    printCourses(studentAggregate);
  }

  public static void printCourses(StudentAggregate studentAggregate) {
    StudentIterator studentIterator = studentAggregate.getStudentIterator();
    while (!studentIterator.isLastStudent()) {
      Student student = studentIterator.nextStudent();
      System.out.println(student.getName());
    }
  }
}
```

## UML

![迭代器模式UML](/files/-LdZ1w0fqlrMYC79hAc-)

### 补充另一个版本的Java/Scala Demo 以及源码解析

## Java Demo\_

## Scala Demo

## UML\_

## 源码解析


# 策略模式

   **策略模式(Strategy Pattern)**：定义一系列算法类，将每一个算法封装起来，并让它们可以相互替换，此模式让算法的变化不会影响到使用算法的用户。\
   如果代码中有很多的if..else.. 就可以使用策略模式来解决这些问题。

## 适用场景

* 系统有很多类，而他们的区别仅仅在于他们的行为不同
* 一个系统需要动态地在几种算法中选择一种

## 优点

* 对开闭原则完美地支持
* 避免使用多重条件转移语句
* 提高算法地保密性和安全性

## 缺点

* 客户端必须知道所有的策略类，并自行决定使用哪一个策略类
* 产生很多的策略类

下面我们引入一种业务场景，超市在店庆的时候，会进行促销，但是促销的策略有多种，满减，立减，返现等不同的方式。

## Golang Demo

```go
package strategy

import "fmt"

type Strategy interface {
    doPromotion()
}

type Activity struct {
    strategy Strategy
}

func NewActivity(strategy Strategy) *Activity {
    return &Activity{strategy: strategy}
}
func (a Activity) executeStrategy() {
    a.strategy.doPromotion()
}

type FanXianStratege struct {
}

func NewFanXianStratege() *FanXianStratege {
    return &FanXianStratege{}
}

func (FanXianStratege) doPromotion() {
    fmt.Println("返现促销")
}

type LiJianStrategy struct {
}

func NewLiJianStrategy() *LiJianStrategy {
    return &LiJianStrategy{}
}

func (LiJianStrategy) doPromotion() {
    fmt.Println("立减促销")
}

type ManJianStrategy struct {
}

func NewManJianStrategy() *ManJianStrategy {
    return &ManJianStrategy{}
}

func (ManJianStrategy) doPromotion() {
    fmt.Println("满减促销")
}
```

```go
package strategy

import "testing"

func Test(t *testing.T) {

    activity618 := NewActivity(NewFanXianStratege())
    activity1111 := NewActivity(NewLiJianStrategy())

    activity618.executeStrategy()
    activity1111.executeStrategy()

}
```

## Java Demo

```java
package tech.selinux.design.pattern.behavioral.strategy;

public interface Strategy {
  void doPromotion();
}
```

```java
package tech.selinux.design.pattern.behavioral.strategy;

public class Activity {
  private Strategy strategy;

  public Activity(Strategy strategy) {
    this.strategy = strategy;
  }

  public void executeStrategy() {
    strategy.doPromotion();
  }
}
```

```java
package tech.selinux.design.pattern.behavioral.strategy;

public class FanXianStrategy implements Strategy {
  @Override
  public void doPromotion() {
    System.out.println("返现促销");
  }
}
```

```java
package tech.selinux.design.pattern.behavioral.strategy;

public class LiJianStrategy implements Strategy {
  @Override
  public void doPromotion() {
    System.out.println("立减促销");
  }
}
```

```java
package tech.selinux.design.pattern.behavioral.strategy;

public class ManJianStrategy implements Strategy {
  @Override
  public void doPromotion() {
    System.out.println("满减促销");
  }
}
```

```java
package tech.selinux.design.pattern.behavioral.strategy;

public class Test {
  public static void main(String[] args) {
    Activity activity618 = new Activity(new LiJianStrategy());
    Activity activity1111 = new Activity(new FanXianStrategy());

    activity618.executeStrategy();
    activity1111.executeStrategy();
  }
}
```

上面的案例中就是策略模式的最简单的应用。但是这并不能满足我们复杂的开发。例如，如果我们在Test类中有这样一段代码的话。

```java
package tech.selinux.design.pattern.behavioral.strategy;

import org.apache.commons.lang3.StringUtils;

public class Test {
  public static void main(String[] args) {
    //    Activity activity618 = new Activity(new LiJianStrategy());
    //    Activity activity1111 = new Activity(new FanXianStrategy());
    //
    //    activity618.executeStrategy();
    //    activity1111.executeStrategy();

    final String strategy = "LIJIAN";

    if (StringUtils.equals(strategy, "LIJIAN")) {
      Activity activity = new Activity(new LiJianStrategy());
      activity.executeStrategy();
    } else if (StringUtils.equals(strategy, "MANJIAN")) {
      Activity activity = new Activity(new ManJianStrategy());
      activity.executeStrategy();
    }
  }
}
```

这样话，我们还是避免不了大量的if..else..的使用。此时，我们就可以结合工厂模式，或者享元模式来进行进一步的优化

## UML

![策略模式UML](/files/-LdZ9hFdGXFide-o-RzQ)

### 补充另一个版本的Java/Scala Demo 以及源码解析

## Java Demo\_

## Scala Demo

## UML\_

## 源码解析


# 解释器模式

**解释器模式(Interpreter Pattern)**:给定一个语言，定义它的文法的一种表示。并定义一个解释器，这个解释器使用该表示来解释语言中的句子。简单来说，就是为了解释一种语言，而为语言创建的解释器。

## 适用场景

* 某个特定类型问题发生频率足够高（例如日志解析,计算器）

## 优点

* 语法由很多类表示，容易改变以及扩展此"语言"

## 缺点

* 当语法规则数目太多时，增加了系统复杂度

在实际的开发工作中，解释器一般会由编程语言本身提供的库来实现，我们一遍不需要太多关心。但是我还是引入一种应用场景来学习一下，就是一个加减计算器。

下面从 [golang-design-pattern](https://github.com/senghoo/golang-design-pattern/tree/master/19_interpreter)引入的一个例子。

## Golang Demo

```go
package interpreter

import (
    "strconv"
    "strings"
)

type Node interface {
    Interpret() int
}

type ValNode struct {
    val int
}

func (n *ValNode) Interpret() int {
    return n.val
}

type AddNode struct {
    left, right Node
}

func (n *AddNode) Interpret() int {
    return n.left.Interpret() + n.right.Interpret()
}

type MinNode struct {
    left, right Node
}

func (n *MinNode) Interpret() int {
    return n.left.Interpret() - n.right.Interpret()
}

type Parser struct {
    exp   []string
    index int
    prev  Node
}

func (p *Parser) Parse(exp string) {
    p.exp = strings.Split(exp, " ")

    for {
        if p.index >= len(p.exp) {
            return
        }
        switch p.exp[p.index] {
        case "+":
            p.prev = p.newAddNode()
        case "-":
            p.prev = p.newMinNode()
        default:
            p.prev = p.newValNode()
        }
    }
}

func (p *Parser) newAddNode() Node {
    p.index++
    return &AddNode{
        left:  p.prev,
        right: p.newValNode(),
    }
}

func (p *Parser) newMinNode() Node {
    p.index++
    return &MinNode{
        left:  p.prev,
        right: p.newValNode(),
    }
}

func (p *Parser) newValNode() Node {
    v, _ := strconv.Atoi(p.exp[p.index])
    p.index++
    return &ValNode{
        val: v,
    }
}

func (p *Parser) Result() Node {
    return p.prev
}
```

```go
package interpreter

import "testing"

func TestInterpreter(t *testing.T) {
    p := &Parser{}
    p.Parse("1 + 2 + 3 - 4 + 5 - 6")
    res := p.Result().Interpret()
    expect := 1
    if res != expect {
        t.Fatalf("expect %d got %d", expect, res)
    }
}
```

## Java Demo

```java
package tech.selinux.design.pattern.behavioral.interpreter;

public interface Node {
  int interpret();
}
```

```java
package tech.selinux.design.pattern.behavioral.interpreter;

public class AddNode implements Node {

  Node left;

  Node right;

  public AddNode(Node left, Node right) {
    this.left = left;
    this.right = right;
  }

  @Override
  public int interpret() {
    return this.left.interpret() + this.right.interpret();
  }
}
```

```java
package tech.selinux.design.pattern.behavioral.interpreter;

public class MinNode implements Node {
  Node left;
  Node right;

  public MinNode(Node left, Node right) {
    this.left = left;
    this.right = right;
  }

  @Override
  public int interpret() {
    return this.left.interpret() - this.right.interpret();
  }
}
```

```java
package tech.selinux.design.pattern.behavioral.interpreter;

public class ValNode implements Node {
  int val;

  public ValNode(int val) {
    this.val = val;
  }

  @Override
  public int interpret() {
    return this.val;
  }
}
```

```java
package tech.selinux.design.pattern.behavioral.interpreter;

import org.apache.commons.lang3.StringUtils;

public class Parser {
  String[] exp;
  int index;
  Node prev;

  public void parse(String expression) {
    exp = StringUtils.split(expression);

    while (true) {
      if (index >= exp.length) {
        return;
      }
      System.out.println(exp[index]);
      switch (exp[index].charAt(0)) {
        case '+':
          index++;
          prev = new AddNode(prev, newValNode());
          break;
        case '-':
          index++;
          prev = new MinNode(prev, newValNode());
          break;
        default:
          prev = newValNode();
          break;
      }
    }
  }

  public Node newValNode() {

    int v = Integer.parseInt(exp[index]);
    index++;

    return new ValNode(v);
  }

  public Node result() {
    return prev;
  }
}
```

```java
package tech.selinux.design.pattern.behavioral.interpreter;

public class Test {

  public static void main(String[] args) {
    Parser parser = new Parser();
    parser.parse("1 + 2 + 3 - 4 + 5 - 6");
    int res = parser.result().interpret();

    System.out.println(res);
  }
}
```


# 观察者模式

**观察者模式(Observer Pattern)**：定义对象之间的一种一对多依赖关系，让多个观察者对象监听主题对象，当主题对象发生变化时，其相关依赖对象皆得到通知并被自动更新。观察者模式的别名包括发布-订阅（Publish/Subscribe）模式、模型-视图（Model/View）模式、源-监听器（Source/Listener）模式或从属者（Dependents）模式。观察者模式是一种对象行为型模式。

## 适用场景

* 关联行为场景，建立一套触发机制

## 优点

* 观察者和被观察者之间建立一个抽象的耦合
* 观察者模式支持广播通信

## 缺点

* 观察者之间有过多的细节依赖，提高时间消耗以及程序复杂度
* 使用要得当，要避免循环调用

在日常生活中，我们有很多这样的使用示例。例如我订阅了某个公众号，当公众号进行了更新之后，所有的订阅者都能收到消息。我对朋友圈的某条动态进行了评论，当有新评论增加时，我也会收到相应的动态更新。

## Golang Demo

首先在这里说明一下，go 中并没有像java 那样，从编程语言层面对设计模式进行支持。因此我们主要还是从代码语义的角度上来进行理解。如果想要严格的理解观察者模式，建议可以结合实际应用场景查看一下java的Demo。

```go
package observer

type Observer interface {
  Update(observable *Observable)
}

type Observable struct {
  content string
  obs     []Observer
}

func NewObservable() *Observable {
  return &Observable{obs: make([]Observer, 0)}
}

//AddObserver 向被观察者的订阅集合中添加观察者
//需要是线程安全的
//如果被观察者已经存在，则不添加
func (o *Observable) AddObserver(observer Observer) {
  o.obs = append(o.obs, observer)
}

func (o *Observable) Notify() {
  for _, observer := range o.obs {
    observer.Update(o)
  }
}

// 更新被订阅者的内容
func (o *Observable) UpdateContent(content string) {
  o.content = content

}
```

```go
package observer

import "fmt"

type Subscriber struct {
  name string
}

func NewSubscriber(name string) *Subscriber {
  return &Subscriber{name: name}
}

func (s *Subscriber) Update(observable *Observable) {
  fmt.Printf("%s  receive %s \n", s.name, observable.content)
}
```

```go
package observer

func ExampleObserver() {
  observable := NewObservable()
  subscriber1 := NewSubscriber("hello1")

  subscriber2 := NewSubscriber("hello2")
  observable.AddObserver(subscriber1)
  observable.AddObserver(subscriber2)
  observable.UpdateContent("world")
  observable.Notify()
  // Output:
  // hello1  receive worl1d
  // hello2  receive world
}
```

## Java Demo

首先定义一个 公众号的类。

```java
package tech.selinux.design.pattern.behavioral.observer;

import java.util.Observable;

/** 公众号 */
public class OfficialAccounts extends Observable {
  private String accountsName;

  public OfficialAccounts(String accountsName) {
    this.accountsName = accountsName;
  }

  public String getAccountsName() {
    return accountsName;
  }

  // 代表状态发生改变
  public void publishArticle(OfficialAccounts accounts, Article article) {
    System.out.println(article.getTitle() + "  on   " + accounts.getAccountsName());
    setChanged();
    notifyObservers(article);
  }
}
```

公众号内的文章是依附于公众号存在的。

```java
package tech.selinux.design.pattern.behavioral.observer;

public class Article {
  private String title;
  private String content;

  public String getContent() {
    return content;
  }

  public void setContent(String content) {
    this.content = content;
  }

  public String getTitle() {
    return title;
  }

  public void setTitle(String title) {
    this.title = title;
  }
}
```

接下来定义用户，用户是观察者，公众号是被观察者。

```java
package tech.selinux.design.pattern.behavioral.observer;

import java.util.Observable;
import java.util.Observer;

/** 对于用户来说，观察的是公众号 观察者是 user，被观察者是 offical accounts */
public class User implements Observer {
  private String nickName;

  public User(String nickName) {
    this.nickName = nickName;
  }

  @Override
  public void update(Observable o, Object arg) {
    OfficialAccounts accounts = (OfficialAccounts) o;
    Article article = (Article) arg;
    StringBuilder sb = new StringBuilder();
    sb.append("用户名：")
        .append(nickName)
        .append(": \n")
        .append("公众号 :")
        .append(accounts.getAccountsName())
        .append("\n")
        .append("文章：")
        .append(article.getTitle())
        .append("\n");
    System.out.println(sb.toString());
  }
}
```

```java
package tech.selinux.design.pattern.behavioral.observer;

public class Test {
  public static void main(String[] args) {
    OfficialAccounts accounts = new OfficialAccounts("Linux 中国");

    User user = new User("PegasusMeteor");
    accounts.addObserver(user);

    User user1 = new User("Pegasus");
    accounts.addObserver(user1);

    Article article = new Article();
    article.setTitle("Linux 未来趋势");
    article.setContent("一片大好");
    accounts.publishArticle(accounts, article);
  }
}
```

## UML

![观察者模式UML](/files/-Lfs6o2oKAmwu3GmRT5-)

### 补充另一个版本的Java/Scala Demo 以及源码解析

## Java Demo\_

## Scala Demo

## UML\_

## 源码解析


# 备忘录模式

**备忘录模式(Memento Pattern)**：在不破坏封装的前提下，捕获一个对象的内部状态，并在该对象之外保存这个状态，这样可以在以后将对象恢复到原先保存的状态。它是一种对象行为型模式，其别名为Token.

## 适用场景

* 保存以及恢复数据相关业务场景
* 想要恢复到之前的状态

## 优点

* 为用户提供一种可恢复的机制
* 存档信息的封装

## 缺点

* 资源占用

下面我们引入一种应用场景，在网站上发表文章的时候，会有一个暂存功能。我们能够暂时保存当前的编辑状态。同时，如果后期有必要的话，我们可以返回之前的状态。

## Golang Demo

```go
type Article struct {
  title   string
  content string
}

func NewArticle(title string, content string) *Article {
  return &Article{title: title, content: content}
}

func (a *Article) addToMemento() *ArticleMemento {
  return NewArticleMemento(a.title, a.content)
}

func (a *Article) undoFromMemento(memento *ArticleMemento) {
  a.title = memento.title
  a.content = memento.content

}
```

```go
type ArticleMemento struct {
  title   string
  content string
}

func NewArticleMemento(title string, content string) *ArticleMemento {
  return &ArticleMemento{title: title, content: content}
}

type ArticleMementoManager struct {
  // 有很多种方式可以实现堆栈的效果 container/list slice 等等
  // 下面我们采用map来模拟一个不是很严谨的stack
  articleMementoStack map[int]*ArticleMemento
  index               int
}
```

```go
func NewArticleMementoManager() *ArticleMementoManager {
  articleMementoStack := make(map[int]*ArticleMemento)
  return &ArticleMementoManager{articleMementoStack: articleMementoStack}
}

func (a *ArticleMementoManager) addMemento(memento *ArticleMemento) {
  a.articleMementoStack[a.index] = memento
  a.index++

}
func (a *ArticleMementoManager) getMemento() *ArticleMemento {
  if a.index > 0 {
    a.index--
    return a.articleMementoStack[a.index]
  }
  return nil

}
```

```go
package memento

import "fmt"

func ExampleMemento() {
  article := NewArticle("hello", "the new world")
  articleMemento := article.addToMemento()
  articleMementoManager := NewArticleMementoManager()
  articleMementoManager.addMemento(articleMemento)

  fmt.Printf("%s,%s\n", article.title, article.content)

  article.content = "the new world2"
  fmt.Printf("%s,%s\n", article.title, article.content)

  article.undoFromMemento(articleMementoManager.getMemento())
  fmt.Printf("%s,%s\n", article.title, article.content)
  // Output:
  // hello,the new world
  // hello,the new world2
  // hello,the new world
}
```

## Java Demo

定义一个Article 类，用来表示在网站上编写的文章。

```java
package tech.selinux.design.pattern.behavioral.memento;

/** 网站上发表的文章 */
public class Article {
  private String title;
  private String content;

  public Article(String title, String content) {
    this.title = title;
    this.content = content;
  }

  public String getTitle() {
    return title;
  }

  public String getContent() {
    return content;
  }

  public void setTitle(String title) {
    this.title = title;
  }

  public void setContent(String content) {
    this.content = content;
  }

  public ArticleMemento saveToMemento() {
    ArticleMemento articleMemento = new ArticleMemento(this.title, this.content);
    return articleMemento;
  }

  public void undoFromMemento(ArticleMemento articleMemento) {
    this.title = articleMemento.getTitle();
    this.content = articleMemento.getContent();
  }

  @Override
  public String toString() {
    return "Article{" + "title='" + title + '\'' + ", content='" + content + '\'' + '}';
  }
}
```

编写一个备忘录类，这个类只能由article类创建，并且作为一个article的快照。这也就意味着，这个类不能被其他的类进行修改。

```java
package tech.selinux.design.pattern.behavioral.memento;

public class ArticleMemento {
  private String title;
  private String content;

  public ArticleMemento(String title, String content) {
    this.title = title;
    this.content = content;
  }

  public String getTitle() {
    return title;
  }

  public String getContent() {
    return content;
  }
}
```

定义一个备忘录管理者的类。利用栈这种数据结构来保存我们之前保存的多个状态。同时，借助栈先进后出的特性，我们能够保证最先出站的状态永远是上一次保存的状态。

```java
package tech.selinux.design.pattern.behavioral.memento;

import java.util.Stack;

public class ArticleMementoManager {
  private final Stack<ArticleMemento> ARTICLE_MEMENTO_STACK = new Stack<ArticleMemento>();

  public ArticleMemento getMemento() {
    ArticleMemento articleMemento = ARTICLE_MEMENTO_STACK.pop();
    return articleMemento;
  }

  public void addMemento(ArticleMemento articleMemento) {
    ARTICLE_MEMENTO_STACK.push(articleMemento);
  }
}
```

```java
package tech.selinux.design.pattern.behavioral.memento;

public class Test {
  public static void main(String[] args) {
    ArticleMementoManager articleMementoManager = new ArticleMementoManager();
    Article article = new Article("Hello", "the new world ");
    ArticleMemento articleMemento = article.saveToMemento();
    articleMementoManager.addMemento(articleMemento);
    System.out.println(article);
    article.setContent("world2");
    System.out.println(article);

    article.undoFromMemento(articleMemento);
    System.out.println(article);
  }
}
```

## UML

![备忘录模式UML](/files/-LfsVhsTKbUxFx7T3e_Y)

### 补充另一个版本的Java/Scala Demo 以及源码解析

## Java Demo\_

## Scala Demo

## UML\_

## 源码解析


# 命令模式

**命令模式(Command Pattern)**：将一个请求封装为一个对象，以便使用不同的对象。命令模式解决了应用程序中对象的职责，以及他们之间的通信方式。

## 适用场景

* 请求调用者和请求接收者需要解耦，使得调用者和接收者不直接交互
* 需要抽象出等待执行的行为

## 优点

* 降低解耦
* 容易扩展新命令或者一组新命令

## 缺点

* 命令的无限扩展会增加类的数量，提高系统的实现复杂度

下面我们引入一种应用场景。在万物互联智能化的今天，大部分的家居产品都可以使用app来进行控制了。就以家里的智能灯为例。我们可以通过一个app控制家里的多个灯。或者对一个灯，进行反复的开关操作。

## Golang Demo

```go
package command

import (
    "fmt"
)

type Command interface {
    execute()
}

type Light struct {
    name string
}

func NewLight(name string) *Light {
    return &Light{name: name}
}

func (l *Light) open() {
    fmt.Println("open light " + l.name)
}
func (l *Light) close() {
    fmt.Println("close light " + l.name)
}

type OpenLightCommand struct {
    light *Light
}

func (o *OpenLightCommand) execute() {
    o.light.open()
}

func NewOpenLightCommand(light *Light) *OpenLightCommand {
    return &OpenLightCommand{light: light}
}

type CloseLightCommand struct {
    light *Light
}

func (c *CloseLightCommand) execute() {
    c.light.close()
}

func NewCloseLightCommand(light *Light) *CloseLightCommand {
    return &CloseLightCommand{light: light}
}

type App struct {
    commandList []Command
}

func NewApp() *App {
    return &App{}
}

func (a *App) addCommand(command Command) {
    a.commandList = append(a.commandList, command)
}

func (a *App) executeCommand() {
    for _, command := range a.commandList {
        command.execute()
    }
    // 清空这个切片
    a.commandList = a.commandList[0:0]
}
```

```go
package command

func ExampleCommand() {
    light := NewLight("小智同学")
    openLightCommand := NewOpenLightCommand(light)
    closeLightCommand := NewCloseLightCommand(light)
    app := NewApp()
    app.addCommand(openLightCommand)
    app.addCommand(closeLightCommand)
    app.executeCommand()
    //Output:
    //open light 小智同学
    //close light 小智同学
}
```

## Java Demo

首先我们定义一个命令接口。

```java
package tech.selinux.design.pattern.behavioral.command;

public interface Command {
  void execute();
}
```

创建一款智能灯。

```java
package tech.selinux.design.pattern.behavioral.command;

// 智能灯
// 有个名字 小智同学
public class Light {
  private String name;

  public Light(String name) {
    this.name = name;
  }

  public void open() {
    System.out.println("open light " + this.name);
  }

  public void close() {
    System.out.println("close light" + this.name);
  }
}
```

设计相应的开灯命令，和关灯命令。

```java
package tech.selinux.design.pattern.behavioral.command;

public class OpenLightCommand implements Command {
  private Light light;

  public OpenLightCommand(Light light) {
    this.light = light;
  }

  @Override
  public void execute() {
    light.open();
  }
}
```

```java
package tech.selinux.design.pattern.behavioral.command;

public class CloseLightCommand implements Command {
  private Light light;

  public CloseLightCommand(Light light) {
    this.light = light;
  }

  @Override
  public void execute() {
    light.close();
  }
}
```

接下来，我们定义一个app类型的模拟类,这里面可以批量的接收和执行command

```java
package tech.selinux.design.pattern.behavioral.command;

import java.util.ArrayList;
import java.util.List;

public class App {
  private List<Command> commandList = new ArrayList<Command>();

  public void addCommand(Command command) {
    commandList.add(command);
  }

  public void executeCommand() {
    for (Command command : commandList) {
      command.execute();
    }
    commandList.clear();
  }
}
```

```java
package tech.selinux.design.pattern.behavioral.command;

public class Test {
  public static void main(String[] args) {
    Light light = new Light("小智同学");

    OpenLightCommand openLightCommand = new OpenLightCommand(light);
    CloseLightCommand closeLightCommand = new CloseLightCommand(light);
    App app = new App();
    app.addCommand(openLightCommand);
    app.addCommand(closeLightCommand);
    app.executeCommand();
  }
}
```

## UML

![命令模式UML](/files/-Lfsrn0p0hYpqSujsVcQ)

### 补充另一个版本的Java/Scala Demo 以及源码解析

## Java Demo\_

## Scala Demo

## UML\_

## 源码解析


# 中介者模式

**中介者模式(Mediator Pattern)**：用一个中介对象（中介者）来封装一系列的对象交互，中介者使各对象不需要显式地相互引用，从而使其耦合松散，而且可以独立地改变它们之间的交互。

## 适用场景

* 系统中对象之间存在复杂的引用关系,产生的相互依赖关系结构混乱且难以理解。
* 交互的公共行为，如果需要改变行为则可以增加新的中介者类。

## 优点

* 将一对多转化成了一对一、降低了程序复杂度
* 类之间解耦

## 缺点

* 中介者过多，导致系统复杂

接下来，我们引入一种应用场景。在工作中，我们会处于某个小组中。小组内的成员之间需要进行互相的沟通和交流。然后我们会有一个小组的工作群，我们在里面进行工作沟通和交流。这个小组群，其实就是我们的中介者。

## Golang Demo

定义一个函数直接调用来模拟静态方法。

```go
package mediator

import (
    "fmt"
)

type Member struct {
    name string
}

func (m *Member) Name() string {
    return m.name
}

func NewMember(name string) *Member {
    return &Member{name: name}
}

func (m *Member) sendMessage(message string) {
    ShowMessage(m, message)
}

// 定义一个函数直接调用来模式静态方法
func ShowMessage(member *Member, message string) {
    fmt.Println("2019-05-28" + "  [" + member.Name() + "]  " + message)
}
```

```go
package mediator

func ExampleMediator() {
    peagsus := NewMember("Peagsus")
    meteor := NewMember("Meteor")
    ShowMessage(peagsus, "hello")
    ShowMessage(meteor, "world")
    //Output:
    //2019-05-28  [Peagsus]  hello
    //2019-05-28  [Meteor]  world
}
```

## Java Demo

我们定义一下小组的成员

```java
package tech.selinux.design.pattern.behavioral.mediator;

public class Member {
  private String name;

  public Member(String name) {
    this.name = name;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public void sendMessage(String message) {
    WorkGroup.showMessage(this, message);
  }
}
```

```java
package tech.selinux.design.pattern.behavioral.mediator;

import java.util.Date;

public class WorkGroup {

  public static void showMessage(Member member, String message) {
    System.out.println(new Date().toString() + "  [" + member.getName() + "]  " + message);
  }
}
```

```java
package tech.selinux.design.pattern.behavioral.mediator;

public class Test {
  public static void main(String[] args) {
    Member peagsus = new Member("Pegasus");
    Member meteor = new Member("Meteor");
    peagsus.sendMessage("hello");
    meteor.sendMessage("world");
  }
}
```

## UML

UML 太简单了，就不贴图了。

### 补充另一个版本的Java/Scala Demo 以及源码解析

## Java Demo\_

## Scala Demo

## UML\_

## 源码解析


# 责任链模式

**职责链模式(Chain of Responsibility Pattern)**：避免请求发送者与接收者耦合在一起，让多个对象都有可能接收请求，将这些对象连接成一条链，并且沿着这条链传递请求，直到有对象处理它为止。

一句话总结：为请求创建一个接收此次请求的链。

## 适用场景

一个请求的处理，需要多个对象中的一个或者多个协作处理。

## 优点

* 请求的发送者和接收者（处理者）进行解耦
* 责任链可以动态组合

## 缺点

* 如果责任链太长或者处理时间太长，影响性能
* 责任链可能太长

下面我们引入一个应用场景，在大部分公司，一个产品上线，或者bug修复上线，通常会经过以下的一个过程。开发提交代码---QA测试---SRE上线，每一个环节都有自己的责任属性，这样就形成了一个责任链。接下来，我们就用代码来实现这个责任链模式。

这里面有一个非常重要的点，就在于Handler 内部有一个自己，即handler。每个责任链节点都要去check一下这个handler是否需要继续向下传递。

## Golang Demo

```go
package chainofresponsibility

import "fmt"

type Product struct {
    name    string
    content string
}

type IHandler interface {
    deploy(product Product)
}

type Handler struct {
    iHandler IHandler
}

func (h *Handler) setNextHandler(iHandler IHandler) {
    h.iHandler = iHandler
}

type QAHandler struct {
    Handler
}

func (qa QAHandler) deploy(product Product) {

    if product.name != "" {
        fmt.Println(product.name + "bug已经修复，QAHandler批准")
        if qa.iHandler != nil {
            qa.iHandler.deploy(product)
        }
    } else {
        fmt.Println(product.name + "没有修复bug，Over")
        return
    }

}

type SREHandler struct {
    Handler
}

func (sre SREHandler) deploy(product Product) {

    if product.name != "" {
        fmt.Println(product.name + "bug已经修复，SREHandler批准")
        if sre.iHandler != nil {
            sre.iHandler.deploy(product)
        }
    } else {
        fmt.Println(product.name + "没有修复bug，Over")
        return
    }

}
```

```go
package chainofresponsibility

func ExampleResponsibility() {
    qaHandler := QAHandler{}
    sreHandler := SREHandler{}

    product := Product{}
    product.name = "big data"
    product.content = "bug fix"

    qaHandler.setNextHandler(sreHandler)
    qaHandler.deploy(product)
    // Output:
    // big databug已经修复，QAHandler批准
    // big databug已经修复，SREHandler批准
}
```

## Java Demo

```java
package tech.selinux.design.pattern.behavioral.chainofresponsibility;

/** 产品名称 */
public class Product {
  private String name;
  private String content;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getContent() {
    return content;
  }

  public void setContent(String content) {
    this.content = content;
  }

  @Override
  public String toString() {
    return "Product{" + "name='" + name + '\'' + ", content='" + content + '\'' + '}';
  }
}
```

```java
package tech.selinux.design.pattern.behavioral.chainofresponsibility;

/** 定义一个类，代表责任链中的每一个环节 */
public abstract class Handler {
  protected Handler handler;

  public void setNextApprover(Handler handler) {
    this.handler = handler;
  }

  public abstract void deploy(Product product);
}
```

```java
package tech.selinux.design.pattern.behavioral.chainofresponsibility;

import org.apache.commons.lang3.StringUtils;

public class QAHandler extends Handler {

  @Override
  public void deploy(Product product) {
    if (StringUtils.isNoneEmpty(product.getContent())) {
      System.out.println(product.getName() + "bug已经修复，QAHandler批准");
      // 判断是否还有下一个责任人
      if (handler != null) {
        handler.deploy(product);
      }
    } else {
      System.out.println(product.getName() + "没有修复bug，Over");
      return;
    }
  }
}
```

```java
package tech.selinux.design.pattern.behavioral.chainofresponsibility;

import org.apache.commons.lang3.StringUtils;

public class SREHandler extends Handler {
  @Override
  public void deploy(Product product) {
    if (StringUtils.isNoneEmpty(product.getContent())) {
      System.out.println(product.getName() + "bug已经修复，SREHandler批准");
      // 判断是否还有下一个责任人
      if (handler != null) {
        handler.deploy(product);
      }
    } else {
      System.out.println(product.getName() + "没有修复bug，Over");
      return;
    }
  }
}
```

```java
package tech.selinux.design.pattern.behavioral.chainofresponsibility;

public class Test {
  public static void main(String[] args) {
    QAHandler qaHandler = new QAHandler();
    SREHandler sreHandler = new SREHandler();
    Product product = new Product();
    product.setName("big data");
    product.setContent("bug fix");

    qaHandler.setNextApprover(sreHandler);
    qaHandler.deploy(product);
  }
}
```

## UML

![责任链模式UML](/files/-Lg37u_Z09GmSEvPeU3l)

### 补充另一个版本的Java/Scala Demo 以及源码解析

## Java Demo\_

## Scala Demo

## UML\_

## 源码解析


# 访问者模式

**访问者模式(Visitor Pattern)**:提供一个作用于某对象结构中的各元素的操作表示，它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

封装用于某数据结构（List、Set、Map）中各种元素的操作,核心是**封装操作**。

## 适用场景

* 当一个数据结构如（List、Set、Map）包含很多类型对象。
* 数据结构与数据操作分离

## 优点

* 增加新的操作很容易，新增一个访问者就可以

## 缺点

* 增加新的数据结构困难
* 具体元素变更比较麻烦

下面我们引入一个应用场景。在很多视频网站上，会有很多的免费视频，以及付费视频。用户去视频网站浏览的时候，就是一个个访问者。接下来我们通过代码来模拟一下这个业务逻辑。

**注意：** 在实际工作中，并不一定常用访问者模式。一单要使用访问者模式，就说明，这个应用场景必须要使用访问者模式来解决问题，或者说使用访问者模式能让问题解决地更优雅。

## Golang Demo

```go
package visitor

import "fmt"

type IVisitor interface {
    visit(interface{})
}

// 定义一个visitor 实现IVisitor 接口
type Visitor struct {
}

// golang 中不支持重载，所以这里的visit 方法，我们需要费劲一点
func (Visitor) visit(i interface{}) {
    switch i.(type) {
    case FreeVideo:
        video := i.(FreeVideo)
        fmt.Println("Free Video " + video.name)
    case VipVideo:
        video := i.(VipVideo)
        fmt.Printf("Vip Video  %v : Price  %d \n", video.name, video.price)
    }
}

// IVideo 和 Video的组合 模拟一个抽象类
type IVideo interface {
    accept(visitor IVisitor)
}
type Video struct {
    name string
}

type FreeVideo struct {
    Video
}

func (f FreeVideo) accept(visitor IVisitor) {
    visitor.visit(f)
}

type VipVideo struct {
    Video
    price int
}

func (v VipVideo) accept(visitor IVisitor) {
    visitor.visit(v)
}
```

```go
package visitor

func ExampleVisitor() {

    videoList := []IVideo{}
    freeVideo := FreeVideo{}
    freeVideo.name = "一条狗的使命"

    vipVideo := VipVideo{}
    vipVideo.name = "流浪地球"
    vipVideo.price = 6

    videoList = append(videoList, freeVideo)
    videoList = append(videoList, vipVideo)

    for _, video := range videoList {
        video.accept(Visitor{})
    }

    // Output:
    // Free Video 一条狗的使命
    // Vip Video  流浪地球 : Price  6
}
```

## Java Demo

定义一个访问者的接口

```java
package tech.selinux.design.pattern.behavioral.visitor;

/** 定义了一个访问者的接口 */
public interface IVisitor {
  void visit(FreeVideo freeVideo);

  void visit(VipVideo vipVideo);
}
```

定义视频的抽象类

```java
package tech.selinux.design.pattern.behavioral.visitor;

public abstract class Video {
  private String name;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  // 是否接受访问者的访问
  public abstract void accept(IVisitor visitor);
}
```

```java
package tech.selinux.design.pattern.behavioral.visitor;

/** 定义了一个免费视频 继承了视频类 */
public class FreeVideo extends Video {
  @Override
  public void accept(IVisitor visitor) {
    visitor.visit(this);
  }
}
```

```java
package tech.selinux.design.pattern.behavioral.visitor;

public class VipVideo extends Video {
  private int price;

  public int getPrice() {
    return price;
  }

  public void setPrice(int price) {
    this.price = price;
  }

  /** 这里会根据传入的类型调用相应的方法 */
  @Override
  public void accept(IVisitor visitor) {
    visitor.visit(this);
  }
}
```

定义了visitor，visitor会根据传入的视频类别来进行访问。

```java
package tech.selinux.design.pattern.behavioral.visitor;

public class Visitor implements IVisitor {
  @Override
  public void visit(FreeVideo freeVideo) {
    System.out.println("Free Video " + freeVideo.getName());
  }

  @Override
  public void visit(VipVideo vipVideo) {
    System.out.println("Vip Video " + vipVideo.getName() + ": Price " + vipVideo.getPrice());
  }
}
```

```java
package tech.selinux.design.pattern.behavioral.visitor;

import java.util.ArrayList;
import java.util.List;

public class Test {
  public static void main(String[] args) {
    List<Video> videoList = new ArrayList<Video>();

    FreeVideo freeVideo = new FreeVideo();
    freeVideo.setName("一条狗的使命");

    VipVideo vipVideo = new VipVideo();
    vipVideo.setName("流浪地球");
    vipVideo.setPrice(6);

    videoList.add(freeVideo);
    videoList.add(vipVideo);

    for (Video video : videoList) {
      video.accept(new Visitor());
    }
  }
}
```

## UML

![访问者模式UML](/files/-Lg6JHDvKnrZAqDVixfe)

### 补充另一个版本的Java/Scala Demo 以及源码解析

## Java Demo\_

## Scala Demo

## UML\_

## 源码解析


# 状态模式

**状态模式(State Pattern)**：允许一个对象在其内部状态改变时改变它的行为，对象看起来似乎修改了它的类

## 适用场景

* 一个对象存个多个状态（不同状态下行为不同）,且状态之间可以可相互切换。

## 优点

* 将不同的状态隔离
* 把各种状态的转换逻辑，分布到State的子类中，减少相互间的依赖
* 增加新的状态非常简单

## 缺点

* 状态多的业务场景导致类目数量增加，导致系统非常复杂。

下面，我们引入一种应用场景。在视频网站上观看视频的时候，一个视频会有几种状态，例如播放，暂停，快进，停止等等。当我们改变了视频的状态之后，视频本身具有的行为也发生了变化，这就是状态模式。下面我们来实现一下这个设计模式。

## Golang Demo

定义四种状态的的一个接口

```go
package state

type IVideoState interface {
    play()
    stop()
    pause()
    speed()
}
```

定义一个VideoContext,用来管理全局的状态。所有的状态变化将在这个Context 结构体下面发生变化。

```go
package state

var PLAY_STATE = PlayState{}
var STOP_STATE = StopState{}
var PAUSE_STATE = PauseState{}
var SPEED_STATE = SpeedState{}

type VideoContext struct {
    videoState IVideoState
}

func NewVideoContext() *VideoContext {
    v := &VideoContext{}
    PLAY_STATE.videoContext = v
    PAUSE_STATE.videoContext = v
    STOP_STATE.videoContext = v
    SPEED_STATE.videoContext = v
    return v
}

func (v *VideoContext) SetVideoState(videoState IVideoState) {
    v.videoState = videoState

}

func (v *VideoContext) play() {
    v.videoState.play()
}

func (v *VideoContext) stop() {
    v.videoState.stop()
}

func (v *VideoContext) pause() {
    v.videoState.pause()
}

func (v *VideoContext) speed() {
    v.videoState.speed()
}
```

接下来定义四种状态，这四种状态都要实现 videostate类，然后能够 与videocontext相关关联。

```go
package state

import "fmt"

type PlayState struct {
    videoContext *VideoContext
}

func (p PlayState) play() {
    fmt.Println("正常播放Video")
}

func (p PlayState) stop() {
    p.videoContext.videoState = STOP_STATE
}

func (p PlayState) pause() {
    p.videoContext.videoState = PAUSE_STATE
}

func (p PlayState) speed() {
    p.videoContext.videoState = SPEED_STATE
}
```

```go
package state

import "fmt"

type PauseState struct {
    videoContext *VideoContext
}

func (p PauseState) play() {
    fmt.Println("正常播放Video")
}

func (p PauseState) stop() {
    p.videoContext.videoState = STOP_STATE
}

func (p PauseState) pause() {
    fmt.Println("暂停播放Video")
}

func (p PauseState) speed() {
    p.videoContext.videoState = SPEED_STATE
}
```

```go
package state

import "fmt"

type SpeedState struct {
    videoContext *VideoContext
}

func (s SpeedState) play() {
    s.videoContext.videoState = PLAY_STATE

}

func (s SpeedState) stop() {
    s.videoContext.videoState = STOP_STATE
}

func (s SpeedState) pause() {
    s.videoContext.videoState = PAUSE_STATE
}

func (s SpeedState) speed() {
    fmt.Println("快进播放Video")

}
```

```go
package state

import "fmt"

type StopState struct {
    videoContext *VideoContext
}

func (s StopState) play() {
    s.videoContext.videoState = PLAY_STATE
}

func (s StopState) stop() {
    fmt.Println("停止播放Video")
}

func (s StopState) pause() {
    fmt.Println("ERROR 停止状态不能 暂停")
}

func (s StopState) speed() {
    fmt.Println("ERROR 停止状态不能 快进")
}
```

前面我们介绍过，四种状态对应的是一个context，所以在我们测试的时候，需要手动设置一下每个状态所指向的context。这与java有所不同，golang 种不允许递归引用，但是java可以。

```go
package state

import (
    "fmt"
    "reflect"
)

func ExampleState() {
    videoContext := NewVideoContext()

    videoContext.SetVideoState(PLAY_STATE)
    fmt.Printf("current state : %v \n", reflect.TypeOf(videoContext.videoState).Name())

    videoContext.pause()
    fmt.Printf("current state : %v \n", reflect.TypeOf(videoContext.videoState).Name())

    videoContext.speed()
    fmt.Printf("current state : %v \n", reflect.TypeOf(videoContext.videoState).Name())

    videoContext.stop()
    fmt.Printf("current state : %v \n", reflect.TypeOf(videoContext.videoState).Name())

    videoContext.speed()
    fmt.Printf("current state : %v \n", reflect.TypeOf(videoContext.videoState).Name())

    //Output:
    // current state : PlayState
    // current state : PauseState
    // current state : SpeedState
    // current state : StopState
    // ERROR 停止状态不能 快进
    // current state : StopState
}
```

## Java Demo

首先我们定义一个视频状态，包含四种视频状态，并且包含了一个视频的上下文。

```java
package tech.selinux.design.pattern.behavioral.state;

public abstract class VideoState {
  protected VideoContext videoContext;

  public void setVideoContext(VideoContext videoContext) {
    this.videoContext = videoContext;
  }

  public abstract void play();

  public abstract void speed();

  public abstract void pause();

  public abstract void stop();
}
```

然后我们来定义这个视频的上下文。上下文里面包含了四种常量状态。同时有一个videostate，用来与VideoState相关联。 有一点要注意，在setVideoState的时候，顺便将自己本身赋值给了VideoState。这样二者就关联了起来。

```java
package tech.selinux.design.pattern.behavioral.state;

public class VideoContext {

  private VideoState videoState;
  public static final PlayState PLAY_STATE = new PlayState();
  public static final StopState STOP_STATE = new StopState();
  public static final PauseState PAUSE_STATE = new PauseState();
  public static final SpeedState SPEED_STATE = new SpeedState();

  public VideoState getVideoState() {
    return videoState;
  }

  public void setVideoState(VideoState videoState) {
    this.videoState = videoState;
    // 把当前的环境设置到上下文
    this.videoState.setVideoContext(this);
  }

  public void play() {
    this.videoState.play();
  }

  public void stop() {
    this.videoState.stop();
  }

  public void pause() {
    this.videoState.pause();
  }

  public void speed() {
    this.videoState.speed();
  }
}
```

实际种的四种状态，我们分别使用Video的四个子类来进行实现。\
play 状态下，可以切换到pause，speed，stop状态。

```java
package tech.selinux.design.pattern.behavioral.state;

/** play 状态下是可以切换到其他状态的 */
public class PlayState extends VideoState {
  @Override
  public void play() {
    System.out.println("正常播放Video");
  }

  @Override
  public void speed() {
    super.videoContext.setVideoState(VideoContext.SPEED_STATE);
  }

  @Override
  public void pause() {
    super.videoContext.setVideoState(VideoContext.PAUSE_STATE);
  }

  @Override
  public void stop() {
    super.videoContext.setVideoState(VideoContext.STOP_STATE);
  }
}
```

pause 状态可以切换到play，stop，speed状态。

```java
package tech.selinux.design.pattern.behavioral.state;

/** 暂停状态能够切换到其他状态 */
public class PauseState extends VideoState {
  @Override
  public void play() {
    super.videoContext.setVideoState(VideoContext.PLAY_STATE);
  }

  @Override
  public void speed() {
    super.videoContext.setVideoState(VideoContext.SPEED_STATE);
  }

  @Override
  public void pause() {
    System.out.println("暂停播放Video");
  }

  @Override
  public void stop() {
    super.videoContext.setVideoState(VideoContext.STOP_STATE);
  }
}
```

speed 状态可以切换到其他三种状态。

```java
package tech.selinux.design.pattern.behavioral.state;

/** 快进状态下是可以切换到其他状态的。 */
public class SpeedState extends VideoState {
  @Override
  public void play() {
    super.videoContext.setVideoState(VideoContext.PLAY_STATE);
  }

  @Override
  public void speed() {
    System.out.println("快进播放Video");
  }

  @Override
  public void pause() {
    super.videoContext.setVideoState(VideoContext.PAUSE_STATE);
  }

  @Override
  public void stop() {
    super.videoContext.setVideoState(VideoContext.STOP_STATE);
  }
}
```

stop 状态除了能够切换为play状态外，其他的状态切换不了，这里要注意。

```java
package tech.selinux.design.pattern.behavioral.state;

/** 停止状态不能切换到其他状态 */
public class StopState extends VideoState {
  @Override
  public void play() {
    super.videoContext.setVideoState(VideoContext.PLAY_STATE);
  }

  @Override
  public void speed() {
    System.out.println("ERROR 停止状态不能 快进");
  }

  @Override
  public void pause() {
    System.out.println("ERROR 停止状态不能 暂停");
  }

  @Override
  public void stop() {
    System.out.println("停止播放Video");
  }
}
```

```java
package tech.selinux.design.pattern.behavioral.state;

public class Test {

  public static void main(String[] args) {
    VideoContext videoContext = new VideoContext();
    // 设置初始状态
    videoContext.setVideoState(new PlayState());
    System.out.println("current state :" + videoContext.getVideoState().getClass().getSimpleName());

    videoContext.pause();
    System.out.println("current state :" + videoContext.getVideoState().getClass().getSimpleName());

    videoContext.speed();
    System.out.println("current state :" + videoContext.getVideoState().getClass().getSimpleName());

    videoContext.stop();
    System.out.println("current state :" + videoContext.getVideoState().getClass().getSimpleName());

    videoContext.speed();
    System.out.println("current state :" + videoContext.getVideoState().getClass().getSimpleName());
  }
}
```

## UML

![状态模式UML](/files/-Lg6nTtgfPN_Uq_9iF1n)

### 补充另一个版本的Java/Scala Demo 以及源码解析

## Java Demo\_

## Scala Demo

## UML\_

## 源码解析


# Java

   从开始使用Java到现在已经有5年的时间了。接下来，就好好整理一下Java中值得注意的核心知识点。


# Java Core


# JVM 如何加载类

* [JVM 如何加载类](https://www.selinux.tech/java/core/pages/-LgMx9-Xch7GRelwSVPZ#jvm-如何加载类)
  * [加载](https://www.selinux.tech/java/core/pages/-LgMx9-Xch7GRelwSVPZ#加载)
  * [链接](https://www.selinux.tech/java/core/pages/-LgMx9-Xch7GRelwSVPZ#链接)
  * [初始化](https://www.selinux.tech/java/core/pages/-LgMx9-Xch7GRelwSVPZ#初始化)

本文转载自 [深入拆解JVM](https://time.geekbang.org/column/article/11523)

我们知道，一个java程序，从写完到运行需要经过几个过程。从JVM视角来看，执行 Java 代码首先需要将它编译而成的 class 文件加载到 JVM 中。加载后的 Java 类会被存放于方法区(Method Area)中。实际运行时，虚拟机会执行方法 区内的代码。

那么，JVM到底是如何加载Java Class的？

从 class 文件到内存中的类，按先后顺 序需要经过加载、链接以及初始化三大步骤。

Java 语言的类型可以分为两大类:基本类型(primitive types)和引用类型 (reference types).Java 将引用类型细分为四种:类、接口、数组类和泛型参数。泛型参数在编译过程会被擦除，数组类是由 JVM直接生成的,其他两种则有对应的字节流。字节流就是常见的Java编译器生成的 class文件。

## 加载

加载，是指查找字节流，并且据此创建类的过程。前面提到，对于数组类来说，它并没有对应的 字节流，而是由 JVM直接生成的。对于其他的类来说，JVM则需要借助类加载器 来完成查找字节流的过程。

以盖房子为例，村里的 小明 要盖个房子，那么按照流程他得先找个建筑师，跟他说想要设计一个房型，比如说“一室、一厅、一卫”。你或许已经听出来了，这里的房型相当于类，而建筑师，就相当于类加载器。

村里有许多建筑师，他们等级森严，但有着共同的祖师爷，叫启动类加载器(boot class loader)。启动类加载器是由 C++ 实现的，没有对应的 Java 对象，因此在 Java 中只能用 null 来指代。换句话说，祖师爷不喜欢像 小明 这样的小角色来打扰他，所以谁也没有祖师爷的 联系方式。

除了启动类加载器之外，其他的类加载器都是 java.lang.ClassLoader 的子类，因此有对应的 Java 对象。这些类加载器需要先由另一个类加载器，比如说启动类加载器，加载至 JVM 中，方能执行类加载。

村里的建筑师有一个潜规则，就是接到单子自己不能着手干，得先给师傅过过目。师傅不接手的情况下，才能自己来。在 JVM中，这个潜规则有个特别的名字，叫双亲委派模型。每当一个类加载器接收到加载请求时，它会先将请求转发给父类加载器。在父类加载器没有找到所请求的类的情况下，该类加载器才会尝试去加载。

在 Java 9 之前，启动类加载器负责加载最为基础、最为重要的类，比如存放在 JRE 的 lib 目录 下 jar 包中的类(以及由虚拟机参数 -Xbootclasspath 指定的类)。除了启动类加载器之外， 另外两个重要的类加载器是扩展类加载器(extension class loader)和应用类加载器 (application class loader)，均由 Java 核心类库提供。

扩展类加载器的父类加载器是启动类加载器。它负责加载相对次要、但又通用的类，比如存放在 JRE 的 `lib/ext` 目录下 jar 包中的类(以及由系统变量 java.ext.dirs 指定的类)。

应用类加载器的父类加载器则是扩展类加载器。它负责加载应用程序路径下的类。(这里的应用程序路径，便是指虚拟机参数 -cp/-classpath、系统变量 java.class.path 或环境变量 CLASSPATH 所指定的路径。)默认情况下，应用程序中包含的类便是由应用类加载器加载的。

Java 9 引入了模块系统，并且略微更改了上述的类加载器1。扩展类加载器被改名为平台类加载 器(platform class loader)。Java SE 中除了少数几个关键模块，比如说 java.base 是由启动 类加载器加载之外，其他的模块均由平台类加载器所加载。

除了由 Java 核心类库提供的类加载器外，我们还可以加入自定义的类加载器，来实现特殊的加载方式。举例来说，我们可以对 class 文件进行加密，加载时再利用自定义的类加载器对其解密。

除了加载功能之外，类加载器还提供了命名空间的作用。这个很好理解，打个比方，咱们这个村不讲究版权，如果你剽窃了另一个建筑师的设计作品，那么只要你标上自己的名字，这两个房型就是不同的。

在 JVM中，类的唯一性是由类加载器实例以及类的全名一同确定的。即便是同一串字节 流，经由不同的类加载器加载，也会得到两个不同的类。在大型应用中，我们往往借助这一特性，来运行同一个类的不同版本。

## 链接

链接，是指将创建成的类合并至 JVM中，使之能够执行的过程。它可分为验证、准备以及解析三个阶段。

验证阶段的目的，在于确保被加载类能够满足 JVM的约束条件。这就好比 小明 需要将 设计好的房型提交给市政部门审核。只有当审核通过，才能继续下面的建造工作。

通常而言，Java 编译器生成的类文件必然满足 JVM的约束条件。因此，这部分我留到讲 解字节码注入时再详细介绍。

准备阶段的目的，则是为被加载类的静态字段分配内存。Java 代码中对静态字段的具体初始 化，则会在稍后的初始化阶段中进行。过了这个阶段，咱们算是盖好了毛坯房。虽然结构已经完 整，但是在没有装修之前是不能住人的。 除了分配内存外，部分 JVM还会在此阶段构造其他跟类层次相关的数据结构，比如说用 来实现虚方法的动态绑定的方法表。

在 class 文件被加载至 JVM之前，这个类无法知道其他类及其方法、字段所对应的具体 地址，甚至不知道自己方法、字段的地址。因此，每当需要引用这些成员时，Java 编译器会生 成一个符号引用。在运行阶段，这个符号引用一般都能够无歧义地定位到具体目标上。

举例来说，对于一个方法调用，编译器会生成一个包含目标方法所在类的名字、目标方法的名字、接收参数类型以及返回值类型的符号引用，来指代所要调用的方法。

解析阶段的目的，正是将这些符号引用解析成为实际引用。如果符号引用指向一个未被加载的类，或者未被加载类的字段或方法，那么解析将触发这个类的加载(但未必触发这个类的链接以 及初始化。)

如果将这段话放在盖房子的语境下，那么符号引用就好比“小明 的房子”这种说法，不管它存 在不存在，我们都可以用这种说法来指代 小明 的房子。实际引用则好比实际的通讯地址，如果 我们想要与 小明 通信，则需要启动盖房子的过程。

JVM规范并没有要求在链接过程中完成解析。它仅规定了:如果某些字节码使用了符号 引用，那么在执行这些字节码之前，需要完成对这些符号引用的解析。

## 初始化

在 Java 代码中，如果要初始化一个静态字段，我们可以在声明时直接赋值，也可以在静态代码 块中对其赋值。 如果直接赋值的静态字段被 final 所修饰，并且它的类型是基本类型或字符串时，那么该字段便 会被 Java 编译器标记成常量值(ConstantValue)，其初始化直接由 JVM完成。除此 之外的直接赋值操作，以及所有静态代码块中的代码，则会被 Java 编译器置于同一方法中，并把它命名为。

类加载的最后一步是初始化，便是为标记为常量值的字段赋值，以及执行方法的过程。Java 虚 拟机会通过加锁来确保类的方法仅被执行一次。

只有当初始化完成之后，类才正式成为可执行的状态。这放在我们盖房子的例子中就是，只有当 房子装修过后，小明 才能真正地住进去。

那么，类的初始化何时会被触发呢?JVM 规范枚举了下述多种触发情况:

1. 当虚拟机启动时，初始化用户指定的主类;
2. 当遇到用以新建目标类实例的 new 指令时，初始化 new 指令的目标类; 3. 当遇到调用静态方法的指令时，初始化该静态方法所在的类;
3. 当遇到访问静态字段的指令时，初始化该静态字段所在的类;
4. 子类的初始化会触发父类的初始化;
5. 如果一个接口定义了 default 方法，那么直接实现或者间接实现该接口的类的初始化，会触 发该接口的初始化;
6. 使用反射 API 对某个类进行反射调用时，初始化这个类;
7. 当初次调用 MethodHandle 实例时，初始化该 MethodHandle 指向的方法所在的类。

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

这段代码是在著名的单例延迟初始化例子中，只有当调用 Singleton.getInstance 时，程序才会访问 LazyHolder.INSTANCE，才会触发对 LazyHolder 的初始化(对应第 4 种情况)，继而新建一个 Singleton 的实例。 由于类初始化是线程安全的，并且仅被执行一次，因此程序可以确保多线程环境下有且仅有一个 Singleton 实例。


# JVM 垃圾回收

* [JVM GC](/java/core/jvm-gc#jvm-gc)
  * [JVM 中的内存区域划分](https://www.selinux.tech/java/core/pages/-LgQOUw4RbT9RDUfE86v#jvm-中的内存区域划分)
    * [第一，程序计数器(PC，Program Counter Register)。](https://www.selinux.tech/java/core/pages/-LgQOUw4RbT9RDUfE86v#第一程序计数器pcprogram-counter-register)
    * [第二，Java 虚拟机栈(Java Virtual Machine Stack)](https://www.selinux.tech/java/core/pages/-LgQOUw4RbT9RDUfE86v#第二java-虚拟机栈java-virtual-machine-stack)
    * [第三，堆(Heap)](https://www.selinux.tech/java/core/pages/-LgQOUw4RbT9RDUfE86v#第三堆heap)
    * [第四，方法区(Method Area)。](https://www.selinux.tech/java/core/pages/-LgQOUw4RbT9RDUfE86v#第四方法区method-area)
    * [第五，运行时常量池(Run-Time Constant Pool)](https://www.selinux.tech/java/core/pages/-LgQOUw4RbT9RDUfE86v#第五运行时常量池run-time-constant-pool)
    * [第六，本地方法栈(Native Method Stack)](https://www.selinux.tech/java/core/pages/-LgQOUw4RbT9RDUfE86v#第六本地方法栈native-method-stack)
  * [引用计数法与可达性分析](https://www.selinux.tech/java/core/pages/-LgQOUw4RbT9RDUfE86v#引用计数法与可达性分析)
  * [Stop-the-world 以及安全点](https://www.selinux.tech/java/core/pages/-LgQOUw4RbT9RDUfE86v#stop-the-world-以及安全点)
  * [垃圾回收的三种方式](https://www.selinux.tech/java/core/pages/-LgQOUw4RbT9RDUfE86v#垃圾回收的三种方式)
  * [Java 常见的垃圾收集器有哪些](https://www.selinux.tech/java/core/pages/-LgQOUw4RbT9RDUfE86v#java-常见的垃圾收集器有哪些)
  * [垃圾回收过程的理解](https://www.selinux.tech/java/core/pages/-LgQOUw4RbT9RDUfE86v#垃圾回收过程的理解)
  * [补充](https://www.selinux.tech/java/core/pages/-LgQOUw4RbT9RDUfE86v#补充)
  * [Q\&A](/java/core/jvm-gc#qa)
  * [参考文献](https://www.selinux.tech/java/core/pages/-LgQOUw4RbT9RDUfE86v#参考文献)

官方JVM [Java Garbage Collection Basics](https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html)

## JVM 中的内存区域划分

[JVM规范](https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-2.html#jvms-2.5)

### 第一，程序计数器(PC，Program Counter Register)。

在 JVM 规范中，每个线程都有它自 己的程序计数器，并且任何时间一个线程都只有一个方法在执行，也就是所谓的当前方法。程序计数器会存储当前线程正在执行的 Java 方法的 JVM 指令地址;或者，如果是在执行本地方 法，则是未指定值(undefined)。

### 第二，Java 虚拟机栈(Java Virtual Machine Stack)

早期也叫 Java 栈。每个线程在创建时 都会创建一个虚拟机栈，其内部保存一个个的栈帧(Stack Frame)，对应着一次次的 Java 方 法调用。 前面谈程序计数器时，提到了当前方法;同理，在一个时间点，对应的只会有一个活动的栈帧， 通常叫作当前帧，方法所在的类叫作当前类。如果在该方法中调用了其他方法，对应的新的栈帧 会被创建出来，成为新的当前帧，一直到它返回结果或者执行结束。JVM 直接对 Java 栈的操作 只有两个，就是对栈帧的压栈和出栈。 栈帧中存储着局部变量表、操作数(operand)栈、动态链接、方法正常退出或者异常退出的定 义等。

### 第三，堆(Heap)

它是 Java 内存管理的核心区域，用来放置 Java 对象实例，几乎所有创建 的 Java 对象实例都是被直接分配在堆上。堆被所有的线程共享，在虚拟机启动时，我们指定 的“Xmx”之类参数就是用来指定最大堆空间等指标。 理所当然，堆也是垃圾收集器重点照顾的区域，所以堆内空间还会被不同的垃圾收集器进行进一 步的细分，最有名的就是新生代、老年代的划分。

### 第四，方法区(Method Area)。

这也是所有线程共享的一块内存区域，用于存储所谓的元 (Meta)数据，例如类结构信息，以及对应的运行时常量池、字段、方法代码等。 由于早期的 Hotspot JVM 实现，很多人习惯于将方法区称为永久代(Permanent Generation)。Oracle JDK 8 中将永久代移除，同时增加了元数据区(Metaspace)。

### 第五，运行时常量池(Run-Time Constant Pool)

这是方法区的一部分。如果仔细分析过反 编译的类文件结构，你能看到版本号、字段、方法、超类、接口等各种信息，还有一项信息就是 常量池。Java 的常量池可以存放各种常量信息，不管是编译期生成的各种字面量，还是需要在 运行时决定的符号引用，所以它比一般语言的符号表存储的信息更加宽泛。

### 第六，本地方法栈(Native Method Stack)

它和 Java 虚拟机栈是非常相似的，支持对本地 方法的调用，也是每个线程都会创建一个。在 Oracle Hotspot JVM 中，本地方法栈和 Java 虚拟机栈是在同一块儿区域，这完全取决于技术实现的决定，并未在规范中强制。

上面各个区域的之间的关系，就可以使用下面的图来进行。

![jvm-architechture](/files/-LgX0cPw-7u7BSDICbVZ)

为了更加直观和清晰的理解，画出了下面的一张内存区域图。

![JVM内存区域划分](/files/-LgX0cPyex7U1-4mU0E7)

## 引用计数法与可达性分析

垃圾回收，顾名思义，便是将已经分配出去的，但却不再使用的内存回收回来，以便能够再次分 配。在 Java 虚拟机的语境下，垃圾指的是死亡的对象所占据的堆空间。这里便涉及了一个关键 的问题:如何辨别一个对象是存是亡?

我们先来讲一种古老的辨别方法:引用计数法(reference counting)。它的做法是为每个对象 添加一个引用计数器，用来统计指向该对象的引用个数。一旦某个对象的引用计数器为 0，则说 明该对象已经死亡，便可以被回收了。

它的具体实现是这样子的:如果有一个引用，被赋值为某一对象，那么将该对象的引用计数器 +1。如果一个指向某一对象的引用，被赋值为其他值，那么将该对象的引用计数器 -1。也就是 说，我们需要截获所有的引用更新操作，并且相应地增减目标对象的引用计数器。

举个例子，假设对象 a 与 b 相互引用，除此之外没有其他引用指向 a 或者 b。在这种情况下，a 和 b 实际上已经死了，但由于它们的引用计数器皆不为 0，在引用计数法的心中，这两个对象 还活着。因此，这些循环引用对象所占据的空间将不可回收，从而造成了内存泄露。

![gc-roots](/files/-LgX0cQ-oeFTd5DKem0F)

目前 Java 虚拟机的主流垃圾回收器采取的是可达性分析算法。这个算法的实质在于将一系列 GC Roots 作为初始的存活对象合集(live set)，然后从该合集出发，探索所有能够被该集合 引用到的对象，并将其加入到该集合中，这个过程我们也称之为标记(mark)。最终，未被探 索到的对象便是死亡的，是可以回收的。

那么什么是 GC Roots 呢?我们可以暂时理解为由堆外指向堆内的引用，一般而言，GC Roots 包括(但不限于)如下几种:

* Java 方法栈桢中的局部变量;
* 已加载类的静态变量;
* JNI handles;
* 已启动且未停止的 Java 线程。

可达性分析可以解决引用计数法所不能解决的循环引用问题。举例来说，即便对象 a 和 b 相互 引用，只要从 GC Roots 出发无法到达 a 或者 b，那么可达性分析便不会将它们加入存活对象 合集之中。

虽然可达性分析的算法本身很简明，但是在实践中还是有不少其他问题需要解决的。

比如说，在多线程环境下，其他线程可能会更新已经访问过的对象中的引用，从而造成误报(将 引用设置为 null)或者漏报(将引用设置为未被访问过的对象)。

误报并没有什么伤害，Java 虚拟机至多损失了部分垃圾回收的机会。漏报则比较麻烦，因为垃 圾回收器可能回收事实上仍被引用的对象内存。一旦从原引用访问已经被回收了的对象，则很有 可能会直接导致 Java 虚拟机崩溃。

## Stop-the-world 以及安全点

怎么解决这个问题呢? 在 Java 虚拟机里，传统的垃圾回收算法采用的是一种简单粗暴的方式， 那便是 Stop-the-world，停止其他非垃圾回收线程的工作，直到完成垃圾回收。这也就造成了 垃圾回收所谓的暂停时间(GC pause)。

Java 虚拟机中的 Stop-the-world 是通过安全点(safepoint)机制来实现的。当 Java 虚拟机 收到 Stop-the-world 请求，它便会等待所有的线程都到达安全点，才允许请求 Stop-the-world 的线程进行独占的工作。

当然，安全点的初始目的并不是让其他线程停下，而是找到一个稳定的执行状态。在这个执行状 态下，Java 虚拟机的堆栈不会发生变化。这么一来，垃圾回收器便能够“安全”地执行可达性 分析。

## 垃圾回收的三种方式

当标记完所有的存活对象时，我们便可以进行死亡对象的回收工作了。主流的基础回收方式可分 为三种。

**第一种是清除(sweep)**，即把死亡对象所占据的内存标记为空闲内存，并记录在一个空闲列表 (free list)之中。当需要新建对象时，内存管理模块便会从该空闲列表中寻找空闲内存，并划 分给新建的对象。

![gc sweep](/files/-LgX0cQ1xPoT8ZVO06lV)

清除这种回收方式的原理及其简单，但是有两个缺点。一是会造成内存碎片。由于 Java 虚拟机 的堆中对象必须是连续分布的，因此可能出现总空闲内存足够，但是无法分配的极端情况。

另一个则是分配效率较低。如果是一块连续的内存空间，那么我们可以通过指针加法(pointer bumping)来做分配。而对于空闲列表，Java 虚拟机则需要逐个访问列表中的项，来查找能够 放入新建对象的空闲内存。

**第二种是压缩(compact)**，即把存活的对象聚集到内存区域的起始位置，从而留下一段连续 的内存空间。这种做法能够解决内存碎片化的问题，但代价是压缩算法的性能开销。

![gc compact](/files/-LgX0cQ3gas8oomrmvOA)

**第三种则是复制(copy)**，即把内存区域分为两等分，分别用两个指针 from 和 to 来维护，并 且只是用 from 指针指向的内存区域来分配内存。当发生垃圾回收时，便把存活的对象复制到 to 指针指向的内存区域中，并且交换 from 指针和 to 指针的内容。复制这种回收方式同样能够 解决内存碎片化的问题，但是它的缺点也极其明显，即堆空间的使用效率极其低下

![gc-copy](/files/-LgX0cQ57wbOG5ajjtEd)

当然，现代的垃圾回收器往往会综合上述几种回收方式，综合它们优点的同时规避它们的缺点。

## Java 常见的垃圾收集器有哪些

实际上，垃圾收集器(GC，Garbage Collector)是和具体 JVM 实现紧密相关的，不同厂商 (IBM、Oracle)，不同版本的 JVM，提供的选择也不同。一般我们说的JVM指的就是Oracle JDK 的JVM.

**Serial GC**，它是最古老的垃圾收集器，“Serial”体现在其收集工作是单线程的，并且在进 行垃圾收集过程中，会进入臭名昭著的“Stop-The-World”状态。当然，其单线程设计也 意味着精简的 GC 实现，无需维护复杂的数据结构，初始化也简单，所以一直是 Client 模式 下 JVM 的默认选项。

从年代的角度，通常将其老年代实现单独称作 Serial Old，它采用了标记 - 整理(Mark-Compact)算法，区别于新生代的复制算法。

Serial GC 的对应 JVM 参数是:

```
-XX:+UseSerialGC
```

**ParNew GC**，很明显是个新生代 GC 实现，它实际是 Serial GC 的多线程版本，最常见的应 用场景是配合老年代的 CMS GC 工作，下面是对应参数

```
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC
```

**CMS(Concurrent Mark Sweep) GC**，基于标记 - 清除(Mark-Sweep)算法，设计目标 是尽量减少停顿时间，这一点对于 Web 等反应时间敏感的应用非常重要，一直到今天，仍 然有很多系统使用 CMS GC。但是，CMS 采用的标记 - 清除算法，存在着内存碎片化问 题，所以难以避免在长时间运行等情况下发生 full GC，导致恶劣的停顿。另外，既然强调了 并发(Concurrent)，CMS 会占用更多 CPU 资源，并和用户线程争抢。

**Parrallel GC**，在早期 JDK 8 等版本中，它是 server 模式 JVM 的默认 GC 选择，也被称作 是吞吐量优先的 GC。它的算法和 Serial GC 比较相似，尽管实现要复杂的多，其特点是新生 代和老年代 GC 都是并行进行的，在常见的服务器环境中更加高效。 开启选项是:

```
-XX:+UseParallelGC
```

另外，Parallel GC 引入了开发者友好的配置项，我们可以直接设置暂停时间或吞吐量等目标， JVM 会自动进行适应性调整，例如下面参数:

```
-XX:MaxGCPauseMillis=value
-XX:GCTimeRatio=N // GC 时间和用户时间比例 = 1 / (N+1)
```

**G1 GC** ,这是一种兼顾吞吐量和停顿时间的 GC 实现，是 Oracle JDK 9 以后的默认 GC 选 项。G1 可以直观的设定停顿时间的目标，相比于 CMS GC，G1 未必能做到 CMS 在最好情 况下的延时停顿，但是最差情况要好很多。

G1 GC 仍然存在着年代的概念，但是其内存结构并不是简单的条带式划分，而是类似棋盘的 一个个 region。Region 之间是复制算法，但整体上实际可看作是标记 - 整理(Mark- Compact)算法，可以有效地避免内存碎片，尤其是当 Java 堆非常大的时候，G1 的优势更 加明显。

G1 吞吐量和停顿表现都非常不错，并且仍然在不断地完善，与此同时 CMS 已经在 JDK 9 中 被标记为废弃(deprecated)，所以 G1 GC 值得你深入掌握。

上面介绍了GC的分析，接下来就来看下JVM堆内存是如何进行分配和回收的。

## 垃圾回收过程的理解

在垃圾收集的过程，对应到 Eden、 Survivor、Tenured 等区域会发生什么变化呢?实际上取决于具体的 GC 方式，先来熟悉一下通常的垃圾收集流程.

第一，Java 应用不断创建对象，通常都是分配在 Eden 区域，当其空间占用达到一定阈值时， 触发 minor GC。仍然被引用的对象(绿色方块)存活下来，被复制到 JVM 选择的 Survivor 区 域，而没有被引用的对象(黄色方块)则被回收。注意，存活对象标记了“数字 1”，这是为了表明对象的存活时间。

![jvm-eden](/files/-LgX0cQ7bqrIxuYvi_7k)

第二， 经过一次 Minor GC，Eden 就会空闲下来，直到再次达到 Minor GC 触发条件，这时 候，另外一个 Survivor 区域则会成为 to 区域，Eden 区域的存活对象和 From 区域对象，都会 被复制到 to 区域，并且存活的年龄计数会被加 1。

![jvm-eden2](/files/-LgX0cQ9HK1ptH58tK1q)

第三， 类似第二步的过程会发生很多次，直到有对象年龄计数达到阈值，这时候就会发生所谓 的晋升(Promotion)过程，如下图所示，超过阈值的对象会被晋升到老年代。这个阈值是可 以通过参数指定:

```
-XX:MaxTenuringThreshold=<N>
```

![jmv-eden3](/files/-LgX0cQBnrPS0fE8ITTh)

后面就是老年代 GC，具体取决于选择的 GC 选项，对应不同的算法。下面是一个简单标记 - 整 理算法过程示意图，老年代中的无用对象被清除后， GC 会将对象进行整理，以防止内存碎片化。

![jvm-tenured](/files/-LgX0cQDRhyFzvLB84dQ)

通常我们把老年代 GC 叫作 Major GC，将对整个堆进行的清理叫作 Full GC，但是这个也没有 那么绝对，因为不同的老年代 GC 算法其实表现差异很大，例如 CMS，“concurrent”就体现 在清理工作是与工作线程一起并发运行的。

## 补充

JVM 的可配置参数列表 [java vmoptions](https://www.oracle.com/technetwork/articles/java/vmoptions-jsp-140102.html)

## Q\&A

**1、什么是本地方法JNI ?**

比如我们希望使用汇编语言 (如 X86\_64 的 SIMD 指令)来提升关键代码的性能;再比如，我们希望调用 Java 核心类库无 法提供的，某个体系架构或者操作系统特有的功能。 在这种情况下，我们往往会牺牲可移植性，在 Java 代码中调用 C/C++ 代码(下面简述为 C 代 码)，并在其中实现所需功能。这种跨语言的调用，便需要借助 Java 虚拟机的 Java Native Interface(JNI)机制。 关于 JNI 的例子，你应该特别熟悉 Java 中标记为native的、没有方法体的方法(下面统称为 native 方法)。当在 Java 代码中调用这些 native 方法时，Java 虚拟机将通过 JNI，调用至对 应的 C 函数(下面将 native 方法对应的 C 实现统称为 C 函数)中。

例如下面的方法，使用native方法定义的方法。

```java
public class Object {
  public native int hashCode();
}
```

**2、为什么第二次GC 的时候，eden 的的对象为什么会被直接复制到s2？**

第一次 从 eden 复制到 s1 。

当eden区的内存再次被使用完时，会触发第二次minor gc，将eden区和S1区中存活的对象拷贝到S2区，并将eden区和S1区的内存清空以备使用。

复制的过程 引用计数会增加

当eden区的内存再次被用完时，会触发第三次minor gc，将eden区和S2区存活的对象拷贝到S1区，并清空eden区和S2区的内存，以备使用。

当引用计数达到一定程度时，就会被复制到 老年代。

老年代没有内存的时候也会触发GC

以此往复.

jvm 中默认的 eden与s0，s1 的比例是 8:1:1

**3、堆与栈的具体区别是什么？**

* 堆和栈之间的主要区别在于栈内存用于存储局部变量和函数调用，而堆内存用于在Java中存储对象。无论在何处，对象是在代码中创建的，例如作为成员变量，局部变量或类变量， 它们总是在Java中的堆空间内创建。
* Java中的每个线程都有自己的堆栈，可以使用-Xss JVM参数指定，类似地，也可以使用JVM选项-Xm 和-Xmx 指定Java程序的堆大小，其中-Xms是堆的起始大小和-Xmx 是java堆的最大大小。
* 如果栈中没有用于存储函数调用或局部变量的内存，JVM将抛出java.lang.StackOverFlowError，而如果没有更多的堆空间用于创建对象，JVM将抛出java.lang.OutOfMemoryError;
* 如果正在使用递归，在哪个方法调用自己，你可以快速填充栈内存。堆栈和堆之间的另一个区别是栈内存的大小比 Java中的堆内存大小要小得多。

![java heap stack defference](/files/-Lh5Q-jblo61OIY5zKFw)

**4、GC复制存活的对象，内存地址会变吗？以前的引用怎么办？**

这里 stackoverflow上的一个question获取能够解答这个疑问。

[After GC , the address of the object in memory be changed and why the object reference still valid?](https://stackoverflow.com/questions/15218438/after-gc-the-address-of-the-object-in-memory-be-changed-and-why-the-object-ref)

gc 复制存活的对象，其原来的地址会发生变化。不过这些jvm就帮我们处理好了，对于程序员来说，这些是无感知的。

## 参考文献

* [深入拆解JVM](https://time.geekbang.org/column/article/13091)
* [JVM 垃圾回收](https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/jvm/JVM垃圾回收.md)
* [JVM 内存区域](https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/jvm/Java内存区域.md)
* [JVM (Java Virtual Machine) Architecture](https://www.javatpoint.com/internal-details-of-jvm)
* [Java 核心技术](https://time.geekbang.org/column/article/10325)
* [Java Virtual Machine(oracle)](https://docs.oracle.com/en/java/javase/11/vm/garbage-collection-enhancements.html)
* [JAVA GARBAGE COLLECTION HANDBOOK](https://plumbr.io/handbook/garbage-collection-in-java)


# JVM G1GC

* [G1GC](/java/core/jvm-g1gc#g1gc)
  * [G1GC 的相关概念](https://www.selinux.tech/java/core/pages/-Lgg9TPT_E3JM8rQqOGO#g1gc-的相关概念)
    * [Region](/java/core/jvm-g1gc#region)
    * [SATB](/java/core/jvm-g1gc#satb)
    * [RSet](/java/core/jvm-g1gc#rset)
    * [Pause Prediction Model](/java/core/jvm-g1gc#pause-prediction-model)
  * [GC 过程](https://www.selinux.tech/java/core/pages/-Lgg9TPT_E3JM8rQqOGO#gc-过程)
    * [G1 GC模式](https://www.selinux.tech/java/core/pages/-Lgg9TPT_E3JM8rQqOGO#g1-gc模式)
  * [GC日志](https://www.selinux.tech/java/core/pages/-Lgg9TPT_E3JM8rQqOGO#gc日志)
    * [Young GC日志](https://www.selinux.tech/java/core/pages/-Lgg9TPT_E3JM8rQqOGO#young-gc日志)
    * [global concurrent marking 日志](https://www.selinux.tech/java/core/pages/-Lgg9TPT_E3JM8rQqOGO#global-concurrent-marking-日志)

本文转载自美团技术博客 [Java Hotspot G1 GC的一些关键技术](https://tech.meituan.com/2016/09/23/g1.html)

## G1GC 的相关概念

G1 GC，全称Garbage-First Garbage Collector，通过-XX:+UseG1GC参数来启用,作为体验版随着JDK 6u14版本面世，在JDK 7u4版本发行时被正式推出，相信熟悉JVM的同学们都不会对它感到陌生。在JDK 9中，G1被提议设置为默认垃圾收集器（JEP 248）。在官网中，是这样描述G1的:

> The Garbage-First (G1) collector is a server-style garbage collector, targeted for multi-processor machines with large memories. It meets garbage collection (GC) pause time goals with a high probability, while achieving high throughput. The G1 garbage collector is fully supported in Oracle JDK 7 update 4 and later releases. The G1 collector is designed for applications that:
>
> * Can operate concurrently with applications threads like the CMS collector.
> * Compact free space without lengthy GC induced pause times.
> * Need more predictable GC pause durations.
> * Do not want to sacrifice a lot of throughput performance.
> * Do not require a much larger Java heap.

从官网的描述中，我们知道G1是一种服务器端的垃圾收集器，应用在多处理器和大容量内存环境中，在实现高吞吐量的同时，尽可能的满足垃圾收集暂停时间的要求。它是专门针对以下应用场景设计的:

* 像CMS收集器一样，能与应用程序线程并发执行.
* 整理空闲空间更快。
* 需要GC停顿时间更好预测.
* 不希望牺牲大量的吞吐性能。
* 不需要更大的Java Heap。

G1收集器的设计目标是取代CMS收集器，它同CMS相比，在以下方面表现的更出色:

* G1是一个有整理内存过程的垃圾收集器，不会产生很多内存碎片。
* G1的Stop The World(STW)更可控，G1在停顿时间上添加了预测机制，用户可以指定期望停顿时间。

在G1的实现过程中，引入了一些新的概念，对于实现高吞吐、没有内存碎片、收集时间可控等功能起到了关键作用。下面我们就一起看一下G1中的这几个重要概念。

### Region

传统的GC收集器将连续的内存空间划分为新生代、老年代和永久代（JDK 8去除了永久代，引入了元空间Metaspace），这种划分的特点是各代的存储地址（逻辑地址，下同）是连续的。如下图所示

![传统gc内存布局](/files/-Lgg6Ui_3G_TeF2ks6h5)

而G1的各代存储地址是不连续的，每一代都使用了n个不连续的大小相同的Region，每个Region占有一块连续的虚拟内存地址。如下图所示:

![G1 GC 内存布局](/files/-Lgg6Uib0leITdBEU1k6)

在上图中，我们注意到还有一些Region标明了H，它代表Humongous，这表示这些Region存储的是巨大对象（humongous object，H-obj），即大小大于等于region一半的对象。H-obj有如下几个特征：

* H-obj直接分配到了old gen，防止了反复拷贝移动。
* H-obj在global concurrent marking阶段的cleanup 和 full GC阶段回收.
* 在分配H-obj之前先检查是否超过 initiating heap occupancy percent和the marking threshold, 如果超过的话，就启动global concurrent marking，为的是提早回收，防止 evacuation failures 和 full GC。

为了减少连续H-objs分配对GC的影响，需要把大对象变为普通的对象，建议增大Region size。

一个Region的大小可以通过参数-XX:G1HeapRegionSize设定，取值范围从1M到32M，且是2的指数。如果不设定，那么G1会根据Heap大小自动决定。相关的设置代码如下：

```cpp
// share/vm/gc_implementation/g1/heapRegion.cpp
// Minimum region size; we won't go lower than that.
// We might want to decrease this in the future, to deal with small
// heaps a bit more efficiently.
#define MIN_REGION_SIZE  (      1024 * 1024 )
// Maximum region size; we don't go higher than that. There's a good
// reason for having an upper bound. We don't want regions to get too
// large, otherwise cleanup's effectiveness would decrease as there
// will be fewer opportunities to find totally empty regions after
// marking.
#define MAX_REGION_SIZE  ( 32 * 1024 * 1024 )
// The automatic region size calculation will try to have around this
// many regions in the heap (based on the min heap size).
#define TARGET_REGION_NUMBER          2048
void HeapRegion::setup_heap_region_size(size_t initial_heap_size, size_t max_heap_size) {
  uintx region_size = G1HeapRegionSize;
  if (FLAG_IS_DEFAULT(G1HeapRegionSize)) {
    size_t average_heap_size = (initial_heap_size + max_heap_size) / 2;
    region_size = MAX2(average_heap_size / TARGET_REGION_NUMBER,
                       (uintx) MIN_REGION_SIZE);
  }
  int region_size_log = log2_long((jlong) region_size);
  // Recalculate the region size to make sure it's a power of
  // 2. This means that region_size is the largest power of 2 that's
  // <= what we've calculated so far.
  region_size = ((uintx)1 << region_size_log);
  // Now make sure that we don't go over or under our limits.
  if (region_size < MIN_REGION_SIZE) {
    region_size = MIN_REGION_SIZE;
  } else if (region_size > MAX_REGION_SIZE) {
    region_size = MAX_REGION_SIZE;
  }
}
```

### SATB

全称是Snapshot-At-The-Beginning，由字面理解，是GC开始时活着的对象的一个快照。它是通过Root Tracing得到的，作用是维持并发GC的正确性。 那么它是怎么维持并发GC的正确性的呢？根据三色标记算法，我们知道对象存在三种状态：

* 白：对象没有被标记到，标记阶段结束后，会被当做垃圾回收掉。
* 灰：对象被标记了，但是它的field还没有被标记或标记完。
* 黑：对象被标记了，且它的所有field也被标记完了。

由于并发阶段的存在，Mutator和Garbage Collector线程同时对对象进行修改，就会出现白对象漏标的情况，这种情况发生的前提是：

* Mutator赋予一个黑对象该白对象的引用。
* Mutator删除了所有从灰对象到该白对象的直接或者间接引用。

对于第一个条件，在并发标记阶段，如果该白对象是new出来的，并没有被灰对象持有，那么它会不会被漏标呢？Region中有两个top-at-mark-start（TAMS）指针，分别为prevTAMS和nextTAMS。在TAMS以上的对象是新分配的，这是一种隐式的标记。对于在GC时已经存在的白对象，如果它是活着的，它必然会被另一个对象引用，即条件二中的灰对象。如果灰对象到白对象的直接引用或者间接引用被替换了，或者删除了，白对象就会被漏标，从而导致被回收掉，这是非常严重的错误，所以SATB破坏了第二个条件。也就是说，一个对象的引用被替换时，可以通过write barrier 将旧引用记录下来。

```cpp
//  share/vm/gc_implementation/g1/g1SATBCardTableModRefBS.hpp
// This notes that we don't need to access any BarrierSet data
// structures, so this can be called from a static context.
template <class T> static void write_ref_field_pre_static(T* field, oop newVal) {
  T heap_oop = oopDesc::load_heap_oop(field);
  if (!oopDesc::is_null(heap_oop)) {
    enqueue(oopDesc::decode_heap_oop(heap_oop));
  }
}
// share/vm/gc_implementation/g1/g1SATBCardTableModRefBS.cpp
void G1SATBCardTableModRefBS::enqueue(oop pre_val) {
  // Nulls should have been already filtered.
  assert(pre_val->is_oop(true), "Error");
  if (!JavaThread::satb_mark_queue_set().is_active()) return;
  Thread* thr = Thread::current();
  if (thr->is_Java_thread()) {
    JavaThread* jt = (JavaThread*)thr;
    jt->satb_mark_queue().enqueue(pre_val);
  } else {
    MutexLockerEx x(Shared_SATB_Q_lock, Mutex::_no_safepoint_check_flag);
    JavaThread::satb_mark_queue_set().shared_satb_queue()->enqueue(pre_val);
  }
}
```

SATB也是有副作用的，如果被替换的白对象就是要被收集的垃圾，这次的标记会让它躲过GC，这就是float garbage。因为SATB的做法精度比较低，所以造成的float garbage也会比较多。

### RSet

全称是Remembered Set，是辅助GC过程的一种结构，典型的空间换时间工具，和Card Table有些类似。还有一种数据结构也是辅助GC的：Collection Set（CSet），它记录了GC要收集的Region集合，集合里的Region可以是任意年代的。在GC的时候，对于old->young和old->old的跨代对象引用，只要扫描对应的CSet中的RSet即可。 逻辑上说每个Region都有一个RSet，RSet记录了其他Region中的对象引用本Region中对象的关系，属于points-into结构（谁引用了我的对象）。而Card Table则是一种points-out（我引用了谁的对象）的结构，每个Card 覆盖一定范围的Heap（一般为512Bytes）。G1的RSet是在Card Table的基础上实现的：每个Region会记录下别的Region有指向自己的指针，并标记这些指针分别在哪些Card的范围内。 这个RSet其实是一个Hash Table，Key是别的Region的起始地址，Value是一个集合，里面的元素是Card Table的Index。

下图表示了RSet、Card和Region的关系.( [出处](http://www.infoq.com/articles/tuning-tips-G1-GC) )

![Remembered Sets](/files/-Lgg6UidqGSAJXue8ULF)

上图中有三个Region，每个Region被分成了多个Card，在不同Region中的Card会相互引用，Region1中的Card中的对象引用了Region2中的Card中的对象，蓝色实线表示的就是points-out的关系，而在Region2的RSet中，记录了Region1的Card，即红色虚线表示的关系，这就是points-into。 而维系RSet中的引用关系靠post-write barrier和Concurrent refinement threads来维护，操作伪代码如下

```cpp
void oop_field_store(oop* field, oop new_value) {
  pre_write_barrier(field);             // pre-write barrier: for maintaining SATB invariant
  *field = new_value;                   // the actual store
  post_write_barrier(field, new_value); // post-write barrier: for tracking cross-region reference
}
```

post-write barrier记录了跨Region的引用更新，更新日志缓冲区则记录了那些包含更新引用的Cards。一旦缓冲区满了，Post-write barrier就停止服务了，会由Concurrent refinement threads处理这些缓冲区日志。 RSet究竟是怎么辅助GC的呢？在做YGC的时候，只需要选定young generation region的RSet作为根集，这些RSet记录了old->young的跨代引用，避免了扫描整个old generation。 而mixed gc的时候，old generation中记录了old->old的RSet，young->old的引用由扫描全部young generation region得到，这样也不用扫描全部old generation region。所以RSet的引入大大减少了GC的工作量。

### Pause Prediction Model

Pause Prediction Model 即停顿预测模型。它在G1中的作用是：

> G1 uses a pause prediction model to meet a user-defined pause time target and selects the number of regions to collect based on the specified pause time target.

G1 GC是一个响应时间优先的GC算法，它与CMS最大的不同是，用户可以设定整个GC过程的期望停顿时间，参数-XX:MaxGCPauseMillis指定一个G1收集过程目标停顿时间，默认值200ms，不过它不是硬性条件，只是期望值。那么G1怎么满足用户的期望呢？就需要这个停顿预测模型了。G1根据这个模型统计计算出来的历史数据来预测本次收集需要选择的Region数量，从而尽量满足用户设定的目标停顿时间。 停顿预测模型是以衰减标准偏差为理论基础实现的：

```cpp
//  share/vm/gc_implementation/g1/g1CollectorPolicy.hpp
double get_new_prediction(TruncatedSeq* seq) {
    return MAX2(seq->davg() + sigma() * seq->dsd(),
                seq->davg() * confidence_factor(seq->num()));
}
```

在这个预测计算公式中：davg表示衰减均值，sigma()返回一个系数，表示信赖度，dsd表示衰减标准偏差，confidence\_factor表示可信度相关系数。而方法的参数TruncateSeq，顾名思义，是一个截断的序列，它只跟踪了序列中的最新的n个元素。

在G1 GC过程中，每个可测量的步骤花费的时间都会记录到TruncateSeq（继承了AbsSeq）中，用来计算衰减均值、衰减变量，衰减标准偏差等：

```cpp
// src/share/vm/utilities/numberSeq.cpp

void AbsSeq::add(double val) {
  if (_num == 0) {
    // if the sequence is empty, the davg is the same as the value
    _davg = val;
    // and the variance is 0
    _dvariance = 0.0;
  } else {
    // otherwise, calculate both
    _davg = (1.0 - _alpha) * val + _alpha * _davg;
    double diff = val - _davg;
    _dvariance = (1.0 - _alpha) * diff * diff + _alpha * _dvariance;
  }
}
```

比如要预测一次GC过程中，RSet的更新时间，这个操作主要是将Dirty Card加入到RSet中，具体原理参考前面的RSet。每个Dirty Card的时间花费通过\_cost\_per\_card\_ms\_seq来记录，具体预测代码如下：

```cpp
//  share/vm/gc_implementation/g1/g1CollectorPolicy.hpp

 double predict_rs_update_time_ms(size_t pending_cards) {
    return (double) pending_cards * predict_cost_per_card_ms();
 }
 double predict_cost_per_card_ms() {
    return get_new_prediction(_cost_per_card_ms_seq);
 }
```

get\_new\_prediction就是我们开头说的方法，现在大家应该基本明白停顿预测模型的实现原理了。

## GC 过程

讲完了一些基本概念，下面我们就来看看G1的GC过程是怎样的。

### G1 GC模式

G1提供了两种GC模式，Young GC和Mixed GC，两种都是完全Stop The World的。

* Young GC：选定所有年轻代里的Region。通过控制年轻代的region个数，即年轻代内存大小，来控制young GC的时间开销。
* Mixed GC：选定所有年轻代里的Region，外加根据global concurrent marking统计得出收集收益高的若干老年代Region。在用户指定的开销目标范围内尽可能选择收益高的老年代Region。

由上面的描述可知，Mixed GC不是full GC，它只能回收部分老年代的Region，如果mixed GC实在无法跟上程序分配内存的速度，导致老年代填满无法继续进行Mixed GC，就会使用serial old GC（full GC）来收集整个GC heap。所以我们可以知道，G1是不提供full GC的。

上文中，多次提到了global concurrent marking，它的执行过程类似CMS，但是不同的是，在G1 GC中，它主要是为Mixed GC提供标记服务的，并不是一次GC过程的一个必须环节。global concurrent marking的执行过程分为四个步骤:

* 初始标记（initial mark，STW）。它标记了从GC Root开始直接可达的对象。
* 并发标记（Concurrent Marking）。这个阶段从GC Root开始对heap中的对象标记，标记线程与应用程序线程并行执行，并且收集各个Region的存活对象信息。
* 最终标记（Remark，STW）。标记那些在并发标记阶段发生变化的对象，将被回收。
* 清除垃圾（Cleanup）。清除空Region（没有存活对象的），加入到free list。

第一阶段initial mark是共用了Young GC的暂停，这是因为他们可以复用root scan操作，所以可以说global concurrent marking是伴随Young GC而发生的。第四阶段Cleanup只是回收了没有存活对象的Region，所以它并不需要STW。

Young GC发生的时机大家都知道，那什么时候发生Mixed GC呢？其实是由一些参数控制着的，另外也控制着哪些老年代Region会被选入CSet。

* G1HeapWastePercent：在global concurrent marking结束之后，我们可以知道old gen regions中有多少空间要被回收，在每次YGC之后和再次发生Mixed GC之前，会检查垃圾占比是否达到此参数，只有达到了，下次才会发生Mixed GC。
* G1MixedGCLiveThresholdPercent：old generation region中的存活对象的占比，只有在此参数之下，才会被选入CSet。
* G1MixedGCCountTarget：一次global concurrent marking之后，最多执行Mixed GC的次数。
* G1OldCSetRegionThresholdPercent：一次Mixed GC中能被选入CSet的最多old generation region数量。

除了以上的参数，G1 GC相关的其他主要的参数有：

| 参数                                 | 含义                                                                                     |
| ---------------------------------- | -------------------------------------------------------------------------------------- |
| -XX:G1HeapRegionSize=n             | 设置Region大小，并非最终值                                                                       |
| -XX:MaxGCPauseMillis               | 设置G1收集过程目标时间，默认值200ms，不是硬性条件                                                           |
| -XX:G1NewSizePercent               | 新生代最小值，默认值5%                                                                           |
| -XX:G1MaxNewSizePercent            | 新生代最大值，默认值60%                                                                          |
| -XX:ParallelGCThreads              | STW期间，并行GC线程数                                                                          |
| -XX:ConcGCThreads=n                | 并发标记阶段，并行执行的线程数                                                                        |
| -XX:InitiatingHeapOccupancyPercent | 设置触发标记周期的 Java 堆占用率阈值。默认值是45%。这里的java堆占比指的是non\_young\_capacity\_bytes，包括old+humongous |

## GC日志

G1收集器的日志与其他收集器有很大不同，源于G1独立的体系架构和数据结构，下面这两段日志来源于美团点评的CRM系统线上生产环境。

### Young GC日志

我们先来看看Young GC的日志：

```cpp
{Heap before GC invocations=12 (full 1):
 garbage-first heap   total 3145728K, used 336645K [0x0000000700000000, 0x00000007c0000000, 0x00000007c0000000)
  region size 1024K, 172 young (176128K), 13 survivors (13312K)
 Metaspace       used 29944K, capacity 30196K, committed 30464K, reserved 1077248K
  class space    used 3391K, capacity 3480K, committed 3584K, reserved 1048576K
2014-11-14T17:57:23.654+0800: 27.884: [GC pause (G1 Evacuation Pause) (young)
Desired survivor size 11534336 bytes, new threshold 15 (max 15)
- age   1:    5011600 bytes,    5011600 total
 27.884: [G1Ergonomics (CSet Construction) start choosing CSet, _pending_cards: 1461, predicted base time: 35.25 ms, remaining time: 64.75 ms, target pause time: 100.00 ms]
 27.884: [G1Ergonomics (CSet Construction) add young regions to CSet, eden: 159 regions, survivors: 13 regions, predicted young region time: 44.09 ms]
 27.884: [G1Ergonomics (CSet Construction) finish choosing CSet, eden: 159 regions, survivors: 13 regions, old: 0 regions, predicted pause time: 79.34 ms, target pause time: 100.00 ms]
, 0.0158389 secs]
   [Parallel Time: 8.1 ms, GC Workers: 4]
      [GC Worker Start (ms): Min: 27884.5, Avg: 27884.5, Max: 27884.5, Diff: 0.1]
      [Ext Root Scanning (ms): Min: 0.4, Avg: 0.8, Max: 1.2, Diff: 0.8, Sum: 3.1]
      [Update RS (ms): Min: 0.0, Avg: 0.3, Max: 0.6, Diff: 0.6, Sum: 1.4]
         [Processed Buffers: Min: 0, Avg: 2.8, Max: 5, Diff: 5, Sum: 11]
      [Scan RS (ms): Min: 0.0, Avg: 0.1, Max: 0.1, Diff: 0.1, Sum: 0.3]
      [Code Root Scanning (ms): Min: 0.0, Avg: 0.1, Max: 0.2, Diff: 0.2, Sum: 0.6]
      [Object Copy (ms): Min: 4.9, Avg: 5.1, Max: 5.2, Diff: 0.3, Sum: 20.4]
      [Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [GC Worker Other (ms): Min: 0.0, Avg: 0.4, Max: 1.3, Diff: 1.3, Sum: 1.4]
      [GC Worker Total (ms): Min: 6.4, Avg: 6.8, Max: 7.8, Diff: 1.4, Sum: 27.2]
      [GC Worker End (ms): Min: 27891.0, Avg: 27891.3, Max: 27892.3, Diff: 1.3]
   [Code Root Fixup: 0.5 ms]
   [Code Root Migration: 1.3 ms]
   [Code Root Purge: 0.0 ms]
   [Clear CT: 0.2 ms]
   [Other: 5.8 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 5.0 ms]
      [Ref Enq: 0.1 ms]
      [Redirty Cards: 0.0 ms]
      [Free CSet: 0.2 ms]
   [Eden: 159.0M(159.0M)->0.0B(301.0M) Survivors: 13.0M->11.0M Heap: 328.8M(3072.0M)->167.3M(3072.0M)]
Heap after GC invocations=13 (full 1):
 garbage-first heap   total 3145728K, used 171269K [0x0000000700000000, 0x00000007c0000000, 0x00000007c0000000)
  region size 1024K, 11 young (11264K), 11 survivors (11264K)
 Metaspace       used 29944K, capacity 30196K, committed 30464K, reserved 1077248K
  class space    used 3391K, capacity 3480K, committed 3584K, reserved 1048576K
}
 [Times: user=0.05 sys=0.01, real=0.02 secs]
```

每个过程的作用如下：

* garbage-first heap total 3145728K, used 336645K \[0x0000000700000000, 0x00000007c0000000, 0x00000007c0000000) 这行表示使用了G1垃圾收集器，total heap 3145728K，使用了336645K.
* region size 1024K, 172 young (176128K), 13 survivors (13312K) Region大小为1M，青年代占用了172个（共176128K），幸存区占用了13个（共13312K）。
* Metaspace used 29944K, capacity 30196K, committed 30464K, reserved 1077248K class space used 3391K, capacity 3480K, committed 3584K, reserved 1048576K java 8的新特性，去掉永久区，添加了元数据区，这块不是本文重点，不再赘述。需要注意的是，之所以有committed和reserved，是因为没有设置MetaspaceSize=MaxMetaspaceSize。
* \[GC pause (G1 Evacuation Pause) (young) GC原因，新生代minor GC。
* \[G1Ergonomics (CSet Construction) start choosing CSet, \_pending\_cards: 1461, predicted base time: 35.25 ms, remaining time: 64.75 ms, target pause time: 100.00 ms] 发生minor GC和full GC时，所有相关region都是要回收的。而发生并发GC时，会根据目标停顿时间动态选择部分垃圾对并多的Region回收，这一步就是选择Region。\_pending\_cards是关于RSet的Card Table。predicted base time是预测的扫描card table时间。
* \[G1Ergonomics (CSet Construction) add young regions to CSet, eden: 159 regions, survivors: 13 regions, predicted young region time: 44.09 ms] 这一步是添加Region到collection set，新生代一共159个Region，13个幸存区Region，这也和之前的（172 young (176128K), 13 survivors (13312K)）吻合。预计收集时间是44.09 ms。
* \[G1Ergonomics (CSet Construction) finish choosing CSet, eden: 159 regions, survivors: 13 regions, old: 0 regions, predicted pause time: 79.34 ms, target pause time: 100.00 ms] 这一步是对上面两步的总结。预计总收集时间79.34ms。
* \[Parallel Time: 8.1 ms, GC Workers: 4] 由于收集过程是多线程并行（并发）进行，这里是4个线程，总共耗时8.1ms（wall clock time）
* \[GC Worker Start (ms): Min: 27884.5, Avg: 27884.5, Max: 27884.5, Diff: 0.1] 收集线程开始的时间，使用的是相对时间，Min是最早开始时间，Avg是平均开始时间，Max是最晚开始时间，Diff是Max-Min（此处的0.1貌似有问题）
* \[Ext Root Scanning (ms): Min: 0.4, Avg: 0.8, Max: 1.2, Diff: 0.8, Sum: 3.1] 扫描Roots花费的时间，Sum表示total cpu time，下同。
* \[Update RS (ms): Min: 0.0, Avg: 0.3, Max: 0.6, Diff: 0.6, Sum: 1.4] \[Processed Buffers: Min: 0, Avg: 2.8, Max: 5, Diff: 5, Sum: 11] Update RS (ms)是每个线程花费在更新Remembered Set上的时间。
* \[Scan RS (ms): Min: 0.0, Avg: 0.1, Max: 0.1, Diff: 0.1, Sum: 0.3] 扫描CS中的region对应的RSet，因为RSet是points-into，所以这样实现避免了扫描old generadion region，但是会产生float garbage。
* \[Code Root Scanning (ms): Min: 0.0, Avg: 0.1, Max: 0.2, Diff: 0.2, Sum: 0.6] 扫描code root耗时。code root指的是经过JIT编译后的代码里，引用了heap中的对象。引用关系保存在RSet中。
* \[Object Copy (ms): Min: 4.9, Avg: 5.1, Max: 5.2, Diff: 0.3, Sum: 20.4] 拷贝活的对象到新region的耗时。
* \[Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0] 线程结束，在结束前，它会检查其他线程是否还有未扫描完的引用，如果有，则”偷”过来，完成后再申请结束，这个时间是线程之前互相同步所花费的时间。
* \[GC Worker Other (ms): Min: 0.0, Avg: 0.4, Max: 1.3, Diff: 1.3, Sum: 1.4] 花费在其他工作上（未列出）的时间。
* \[GC Worker Total (ms): Min: 6.4, Avg: 6.8, Max: 7.8, Diff: 1.4, Sum: 27.2] 每个线程花费的时间和。
* \[GC Worker End (ms): Min: 27891.0, Avg: 27891.3, Max: 27892.3, Diff: 1.3] 每个线程结束的时间。
* \[Code Root Fixup: 0.5 ms] 用来将code root修正到正确的evacuate之后的对象位置所花费的时间。
* \[Code Root Migration: 1.3 ms] 更新code root 引用的耗时，code root中的引用因为对象的evacuation而需要更新。
* \[Code Root Purge: 0.0 ms] 清除code root的耗时，code root中的引用已经失效，不再指向Region中的对象，所以需要被清除。
* \[Clear CT: 0.2 ms] 清除card table的耗时。
* \[Other: 5.8 ms] \[Choose CSet: 0.0 ms] \[Ref Proc: 5.0 ms] \[Ref Enq: 0.1 ms] \[Redirty Cards: 0.0 ms] \[Free CSet: 0.2 ms] 其他事项共耗时5.8ms，其他事项包括选择CSet，处理已用对象，引用入ReferenceQueues，释放CSet中的region到free list。
* \[Eden: 159.0M(159.0M)->0.0B(301.0M) Survivors: 13.0M->11.0M Heap: 328.8M(3072.0M)->167.3M(3072.0M)] 新生代清空了，下次扩容到301MB。

### global concurrent marking 日志

对于global concurrent marking过程，它的日志如下所示:

```cpp
66955.252: [G1Ergonomics (Concurrent Cycles) request concurrent cycle initiation, reason: occupancy higher than threshold, occupancy: 1449132032 bytes, allocation request: 579608 bytes, threshold: 1449
551430 bytes (45.00 %), source: concurrent humongous allocation]
2014-12-10T11:13:09.532+0800: 66955.252: Application time: 2.5750418 seconds
 66955.259: [G1Ergonomics (Concurrent Cycles) request concurrent cycle initiation, reason: requested by GC cause, GC cause: G1 Humongous Allocation]
{Heap before GC invocations=1874 (full 4):
 garbage-first heap   total 3145728K, used 1281786K [0x0000000700000000, 0x00000007c0000000, 0x00000007c0000000)
  region size 1024K, 171 young (175104K), 27 survivors (27648K)
 Metaspace       used 116681K, capacity 137645K, committed 137984K, reserved 1171456K
  class space    used 13082K, capacity 16290K, committed 16384K, reserved 1048576K
 66955.259: [G1Ergonomics (Concurrent Cycles) initiate concurrent cycle, reason: concurrent cycle initiation requested]
2014-12-10T11:13:09.539+0800: 66955.259: [GC pause (G1 Humongous Allocation) (young) (initial-mark)
…….
2014-12-10T11:13:09.597+0800: 66955.317: [GC concurrent-root-region-scan-start]
2014-12-10T11:13:09.597+0800: 66955.318: Total time for which application threads were stopped: 0.0655753 seconds
2014-12-10T11:13:09.610+0800: 66955.330: Application time: 0.0127071 seconds
2014-12-10T11:13:09.614+0800: 66955.335: Total time for which application threads were stopped: 0.0043882 seconds
2014-12-10T11:13:09.625+0800: 66955.346: [GC concurrent-root-region-scan-end, 0.0281351 secs]
2014-12-10T11:13:09.625+0800: 66955.346: [GC concurrent-mark-start]
2014-12-10T11:13:09.645+0800: 66955.365: Application time: 0.0306801 seconds
2014-12-10T11:13:09.651+0800: 66955.371: Total time for which application threads were stopped: 0.0061326 seconds
2014-12-10T11:13:10.212+0800: 66955.933: [GC concurrent-mark-end, 0.5871129 secs]
2014-12-10T11:13:10.212+0800: 66955.933: Application time: 0.5613792 seconds
2014-12-10T11:13:10.215+0800: 66955.935: [GC remark 66955.936: [GC ref-proc, 0.0235275 secs], 0.0320865 secs]
 [Times: user=0.05 sys=0.00, real=0.03 secs]
2014-12-10T11:13:10.247+0800: 66955.968: Total time for which application threads were stopped: 0.0350098 seconds
2014-12-10T11:13:10.248+0800: 66955.968: Application time: 0.0001691 seconds
2014-12-10T11:13:10.250+0800: 66955.970: [GC cleanup 1178M->632M(3072M), 0.0060632 secs]
 [Times: user=0.02 sys=0.00, real=0.01 secs]
2014-12-10T11:13:10.256+0800: 66955.977: Total time for which application threads were stopped: 0.0088462 seconds
2014-12-10T11:13:10.257+0800: 66955.977: [GC concurrent-cleanup-start]
2014-12-10T11:13:10.259+0800: 66955.979: [GC concurrent-cleanup-end, 0.0024743 secs
```

这次发生global concurrent marking的原因是：humongous allocation，上面提过在巨大对象分配之前，会检测到old generation 使用占比是否超过了 initiating heap occupancy percent（45%），因为 1449132032(used)+ 579608(allocation request:) > 1449551430(threshold)，所以触发了本次global concurrent marking。对于具体执行过程，上面的表格已经详细讲解了。值得注意的是上文中所说的initial mark往往伴随着一次YGC，在日志中也有体现：GC pause (G1 Humongous Allocation) (young) (initial-mark)。

**本文转载自美团技术博客** [Java Hotspot G1 GC的一些关键技术](https://tech.meituan.com/2016/09/23/g1.html)


# JVM G1GC Q\&A

前面的文章中，我们援引了美团技术博客的文章，简单介绍和了解了G1GC。但是心中还是有很多疑问，所以这篇文章，我就将这些疑惑一一列举出来，并一一解答。争取做到对G1GC有一个深入的详细的了解。

G1 指的是 Garbage First,GC 在JVM 中有两个含义，内存的分配和针对已分配内存的回收。

## 参考

* 《JVM G1 源码分析和调优》-- 彭成寒编著
* [G1GC 参数调优](https://www.oracle.com/cn/technical-resources/articles/java/g1gc.html)
* [The Garbage First Garbage Collector](https://www.oracle.com/java/technologies/javase/hotspot-garbage-collection.html)

## G1 的基本概念

### G1GC的优势

#### 并行与并发

* 并行性:G1在回收期间，可以有多个 GC 线程同时工作，有效利用多核计算能力。
* 并发性:G1拥有与应用程序交替执行的能力，部分工作可以和应用程序同时执行，因此，一般来说，不会在整个回收阶段发生完全阻塞应用程序的情况

#### 分代收集

* 从分代上看，G1依然属于分代型垃圾回收器，它会区分年轻代和老年代，年轻代依然有Eden区和Survivor区。但从堆的结构上看，它不要求整个Eden区、年轻代或者老年代都是连续的，也不再坚持固定大小和固定数量。
* 将堆空间分为若干个区域（Region），这些区域中包含了逻辑上的年轻代和老年代。
* 和之前的各类回收器不同，它同时兼顾年轻代和老年代。对比其他回收器，或者工作在年轻代，或者工作在老年代；

#### 空间整合

* CMS：“标记-清除”算法、内存碎片、若干次GC后进行一次碎片整理
* G1将内存划分为一个个的region。内存的回收是以region作为基本单位的。Region之间是复制算法，但整体上实际可看作是标记-压缩（Mark-Compact）算法，两种算法都可以避免内存碎片。这种特性有利于程序长时间运行，分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC。尤其是当Java堆非常大的时候，G1的优势更加明显。

#### 可预测的停顿时间模型

**可预测的停顿时间模型（即：软实时soft real-time**

这是G1相对于CMS的另一大优势，G1除了追求低停顿外，还能建立可预测的停顿时间模型，能让使用者明确指定在一个长度为M毫秒的时间片段内，消耗在垃圾收集上的时间不得超过N毫秒。

* 由于分区的原因，G1可以只选取部分区域进行内存回收，这样缩小了回收的范围，因此对于全局停顿情况的发生也能得到较好的控制。
* G1跟踪各个Region里面的垃圾堆积的价值大小（回收所获得的空间大小以及回收所需时间的经验值），在后台维护一个优先列表，每次根据允许的收集时间，优先回收价值最大的Region。保证了G1收集器在有限的时间内可以获取尽可能高的收集效率。
* 相比于CMS GC，G1未必能做到CMS在最好情况下的延时停顿，但是最差情况要好很多。

### 如何设置Heap Region的大小

**G1 中每个Region的的大小都是相同的，如何设置Heap Region的大小？设置HR的时候有哪些考虑**?

HR 的大小直接影响分配和回收的效率。HR过大，可以存放多个对象，分配效率高，但是回收效率低。过小，则分配效率低下。为了均衡二者，所以HR设置了一个上下限，分别是1MB,2MB,4MB,...,32MB,也就是2的指数次幂。默认情况下，整个堆空间有2048个HR(该值可以通过最小的堆分区大小计算出来)。

* G1HeapRegionSize 可以用来指定HR的大小，一般默认为0.
* 不指定的时候，由G1自行推断。

按照默认值来计算的话，G1可以管理的最大内存为 2048×32MB = 64G。假设 xms=32G,xmx=128G,那么计算过程如下：

* average\_heap\_size=(32GB+128GB)/2= 80GB 判断是否设置过分区大小，如果有就使用，没有则根据初始内存和最大分配内存，获得平均值
* region\_size=max(80GB/2048,1m)= 40M  并根据HR的个数得到Region的大小,和Region的下限进行比较，取两者的最大值。
* region\_size= 32M  对region\_size 按2的幂次进行对齐，保证其落在上下限范围内

那这样计算出来的每个 HR 的大小就是32MB，则根据最小内存和最大内存的计算，HR个数的变化范围就是从1024 到4096个。

### G1 大对象不使用 新生代，直接进入老年代，那什么是大对象？

简单来说，就是region\_size 的一半。

### 新生代大小如何设置？

新生代大小指的是新生代内存空间的大小。G1中还新增了两个参数G1MaxNewSizePercent 和G1NewSizePercent用于控制新生代的大小。 整体逻辑如下：

* 如果设置新生代最大值（MaxNewSize）和最小值（NewSize），可以根据这些计算出最大的分区和最小的分区，注意设置了Xmn等价与设置了MaxNewSize和NewSize，且 NewSize=MaxNewSize。
* 如果设置 最大值（MaxNewSize）和最小值（NewSize） ，又设置了NewRatio，则忽略NewRatio。
* 如果没有设置最大值（MaxNewSize）和最小值（NewSize），但是设置了NewRatio，则最大值和最小值相同 (= 整个heap/(NewRatio+1))
* 如果没有设置最大值（MaxNewSize）和最小值（NewSize），或者只设置了其中一个，那么G1将根据G1MaxNewSizePercent （默认60）和G1NewSizePercent（默认5）占整个堆空间的比例来计算最大最小值。

### 分配新的分区时，如何扩展，一次扩展多少？

G1 是自适应扩展内存的， 参数 -XX:GCTimeRatio 表示GC与应用的耗时时间比，默认为9.即G1 GC时间与总时间占比不超过10%时，不需要动态扩展，当GC超过这个值时，可以动态扩展。计算方式是 100 × （1.0 /(1.0 + GCTimeRatio）。扩展时有一个参数，G1ExpandByPercentOfAvailable(默认20),即每次都从未提交的内存中申请20%。

### CardTable And RSet(Remember Set)

CardTable 在之前的GC中就已经存在了，它存在的目的是为了对内存的引用关系做标记。从而根据引用关系遍历活跃对象。 关于两者的介绍可以参看前面的文章。

### 参数介绍和调优

* G1HeapRegionSize 指定堆分区的大小，分区大小可以指定，也可以不指定，不指定时由内存管理器自动推断堆分区大小。
* xms/xmx 指定堆空间最小/最大值
* GCTimeRatio 指的是GC时间与整体时间的占比。前面我们已经介绍过计算，增大该值，能够减少GC占用，但是后果就是动态扩展内存更容易发生。
* G1NewSizePercent是一个实验参数。需要使用-XX:+UnlockExperimentalVMOptions 配合才能改变选项。有实验表明G1在回收Eden分区的时候，大概每GB需要100ms，所以可以根据停顿时间进行相应的调整。这个值在内存比较多大的时候，可以相应的减少。

注意： G1 中不需要设置MaxNewSize、NewSize、Xmn和NewRatio，原因是，一，G1对内存的管理是不连续的，即使重新分配一个堆分区代价也不高，G1的目标满足 垃圾收集停顿，如果设置了固定分区，则G1不能调整新生代的大小，可能就满足不了垃圾收集停顿了。

## G1 的对象分配

G1 提供了两种对象分配策略： 基于线程本地分配缓冲区（Thread Local Allocation Buffer，TLAB）的快速分配和慢速分配。 无论快速分配还是慢速分配，都应该在STW（Stop the World）之外进行调用。

在分配线程对象时，从JVM 堆中分配一个固定大小的内存区域将于作为线程的私有缓冲区，这个缓冲区就是TLAB。只有为线程分配TLAB时候才需要锁定JVM。也就是加锁。其实这个比较好理解，在Java中线程是资源分配的最小单位。不同线程不共享TLAB.

当我们需要去分配一个对象时，优先从当前线程的TLAB去分配，不需要全局锁，因此就达到了快速分配的目的。

当不能进行快速分配时，就会进入慢速分配，而且慢的过程中会有一些 诸如大对象直接分配到老年代，分配不会收需要先进行GC等，失败一定次数之后，则分配失败。

### G1垃圾回收的时机

* 分配时发生回收，快速分配和慢速分配时都有可能存在内存不足，都有可能发生回收，回收之后再继续分配。
* 外部调用的回收，例如显式地调用了system.gc 。 JNI（Java Native Interface） 代码进入了临界区(synchronized)，为了保证安全需要加锁，加锁又发出了GC请求，导致GC等。

### 参数介绍和调优

* 在优化调试TLAB的时候，在调试环境可以打开PrintTLAB来观察TLAB的分配和使用情况
* UseTLAB，指是否打开TLAB，大量的实验证明使用TLAB能够加速TLAB的分配和使用的情况
* ResizeTLAB，是否允许动态调整。基准测试表明，使用动态调整TLAB大小，效率更高
* TLABSize，指设置TLAB的大小，实际使用中不要设置，设置之后就不能动态调整了。

## 新生代回收

内存分配时，剩余空间不能满足分配要求就会优先触发新生代回收（Young GC ，YGC）。

为了便于理解，下面用自己的话进行整理描述：

* 收集之前先STW.
* 选择要收集的区域，对于YGC来说，要收集的就是整个新生代。
* 进行并行任务处理。
* 将符合条件的对象复制到新的Survivor区,对象的field入栈等待复制处理.
* 处理老年代到新生代的代际引用，即更新RSet（前面的文章中有介绍）
* 对栈中的对象进行深度递归遍历复制对象。

### 参数介绍和调优

* ParallelGCThreads ,默认值为0，表示并行执行GC的线程个数。G1可以根据CPU的核数自动推断线程数。GC是CPU密集型，通常来说，线程个数不应该超过CPU核数，一般不用设置该值。
* ResizePLAB，默认为true。表示在垃圾回收之后会根据内存的使用情况来调整PLAB（promotion local allocation buffer）的大小，在一些测试中发现关闭这个参数可能有更好的效果。
* SurvivorRatio，默认值为8，指Eden和一个Survivor分区之间的比例。默认8:1:1。G1 中并会因为增大这个值，就导致Eden变小，因为Eden时根据GC的时间来预测的。

## 混合回收

混合回收可以总结为两个阶段：

* 第一阶段： 并发标记，目的就是识别老年代分区中的活跃对象，并计算分区中垃圾对象所占空间的多少，用于垃圾回收过程中判断是否回收分区
  * 初始标记子阶段
  * 并发标记子阶段
  * 再标记子阶段
  * 清理阶段
* 第二阶段： 垃圾回收。这个过程和新生代回收的步骤完全一致，重用了新生代的逻辑。最大的区别就是，不仅要回收新生代，还要回收并发标记中识别到的垃圾多的老年代分区。

下面就根据混合回收发生的逻辑顺序依次介绍一下这些阶段：

#### 初始标记子阶段

标记所有的根对象（根对象，全局对象，JNI对象）,根是对象图的起点，需要STW.混合回收的初始标记与YGC的初始标记几乎一样。实际上就是借用了YGC之后的结果，即Survivor分区作为根，所以混合回收一定发生再YGC之后，且不需要再一次进行初始标记。

#### 并发标记子阶段

当YGC执行结束之后，如果发现满足并发标记的条件，并发线程就开始并发标记。根据新生代的Survivor分区以及老年代的Rset开始并发标记。并发标记会对所有的分区进行标记，这个阶段并不需要STW,这时标记线程和应用程序线程同时运行。

#### 再标记子阶段

这是最后一个标记阶段。这时，G1需要暂停一下，找出所有未被访问到的对象，同时完成存活内存数据计算。

这个阶段也是并行执行的，通过参数 -XX:ParallelGCThreads 可以设置GC 暂停时可用端的GC 线程数。

#### 清理子阶段

再标记之后进入清理子阶段，也是需要STW的。清理子阶段需要完成虾米那的操作。

* 统计存活对象，主要是利用RSet和位图来实现。
* 交换标记位图，并为下次并发标记做准备
* 重置RSet，此时老年代已经标记完，如果标记后的分区没有引用对象，这说明引用已经发生改变，可以删除原来RSet里面的引用关系。
* 把空闲分区放到空闲分区列表中，这里的空闲指的是全部是垃圾对象的分区，如果分区还有任何分区活跃对象都不会被释放，真正释放是在混合GC中。

这个阶段容易让人误解，清理阶段并没有真正的GC，也不会执行存活对象的拷贝，极端情况下，该阶段结束之后，空闲分区列表和JVM内存的使用情况可能毫无变化。

#### 混合回收阶段

这个阶段是辉进行真正的GC的。 与YGC 一样，第一个步骤是从分区中选出若干个进行回收，这些被选中的分区称为Collect Set（简称CSet）；第二个步骤是把存活的对象复制到空闲的分区中区，同时把这些已经被回收的分区放到空闲分区列表中。垃圾回收总是要在一次新的YGC开始才会发生。

### 并发标记法---三色标记法

三色标记法是一个逻辑上的抽象

* 白色，没有被收集器表标记的对象
* 灰色，自身被标记到，但是其拥有的field字段引用到其他对象还没有被处理。
* 黑色, 自己本身被标记，同时field引用的对象也被标记。

这里虽然讲的很简单，但是实际实现过程中却非常复杂，这里先略过了。

### 参数介绍和调优

* InitiationHeapOccupancyPercent(IHOP),默认值是45，这个值是启用并发标记的先决条件。只有当老年代内存占总空间45%之后才会启动并发标记任务。增加该值，可能导致 并发标记花费更多时间。也会导致YGC或者MixedGC收集时的分区变少，还有可能导致FullGC。根据经验，这个值通常根据整体应用占用的平均值来设置，比平均内存稍微高一点此时性能最好（即YGC 和 MixedGC 比较快，FGC比较少），IHOP的设置非常有用，但是设置IHOP并不容易，需要不断尝试。
* G1ReservePercent,默认是10，当发现GC晋升失败导致FGC，可以增大这个值。
* HeapSizePerGCThread，默认为64M，可以简单地理解为每64M分配一个线程。

## FULL GC

DK 10 之前的FGC都是串行GC,但是不管是串行还是并行，过程和步骤都是一样的。而且采用的标记清除算法。

* 标记活跃对象
* 计算新对象的地址
* 把所有的引用都更新到新的地址上面
* 移动对象完成压缩

### 参数介绍和调优

* MinHeapFreeRatio，用于判断是否可以扩展堆空间。增大该值扩展概率变小，减少该值扩展几率变大
* MaxHeapFreeRatio，用于判断是否可以收缩堆空间。增大该值收缩概率变小，减少该值收缩几率变大

## 实践

找到一台运行Java程序的机器，运行`jps`命令,找到相应的进程ID，然后运行 `jmap -heap pid`就可以查看到该进程的堆栈以及GC信息。

```
jmap -heap 15784
Picked up JAVA_TOOL_OPTIONS: -Duser.timezone=UTC -Dfile.encoding=UTF-8 -Dsun.jnu.encoding=UTF-8
Attaching to process ID 15784, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.121-b13

using thread-local object allocation.
Garbage-First (G1) GC with 4 thread(s)

Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 5368709120 (5120.0MB)
   NewSize                  = 1363144 (1.2999954223632812MB)
   MaxNewSize               = 3221225472 (3072.0MB)
   OldSize                  = 5452592 (5.1999969482421875MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 2097152 (2.0MB)

Heap Usage:
G1 Heap:
   regions  = 2560
   capacity = 5368709120 (5120.0MB)
   used     = 1002454008 (956.0146408081055MB)
   free     = 4366255112 (4163.9853591918945MB)
   18.67216095328331% used
G1 Young Generation:
Eden Space:
   regions  = 436
   capacity = 3353346048 (3198.0MB)
   used     = 914358272 (872.0MB)
   free     = 2438987776 (2326.0MB)
   27.267041901188243% used
Survivor Space:
   regions  = 14
   capacity = 29360128 (28.0MB)
   used     = 29360128 (28.0MB)
   free     = 0 (0.0MB)
   100.0% used
G1 Old Generation:
   regions  = 29
   capacity = 1986002944 (1894.0MB)
   used     = 58735608 (56.01464080810547MB)
   free     = 1927267336 (1837.9853591918945MB)
   2.9574783953593173% used
```


# JVM 与 Hbase

* [HBase 调整G1GC](https://www.selinux.tech/java/core/pages/-LggfkvjQQpeojTzey2Q#hbase-调整g1gc)
  * [HBase的G1GC](https://www.selinux.tech/java/core/pages/-LggfkvjQQpeojTzey2Q#hbase的g1gc)
  * [参数配置](https://www.selinux.tech/java/core/pages/-LggfkvjQQpeojTzey2Q#参数配置)
  * [如何调整HBase集群](https://www.selinux.tech/java/core/pages/-LggfkvjQQpeojTzey2Q#如何调整hbase集群)
    * [开始之前: GC and HBase monitoring](https://www.selinux.tech/java/core/pages/-LggfkvjQQpeojTzey2Q#开始之前-gc-and-hbase-monitoring)
    * [Step 0：推荐默认值](https://www.selinux.tech/java/core/pages/-LggfkvjQQpeojTzey2Q#step-0推荐默认值)
    * [Step 1:确定预期的HBase最大使用率](https://www.selinux.tech/java/core/pages/-LggfkvjQQpeojTzey2Q#step-1确定预期的hbase最大使用率)
    * [Step 2: 设置堆大小，IHOP和Eden大小](https://www.selinux.tech/java/core/pages/-LggfkvjQQpeojTzey2Q#step-2-设置堆大小ihop和eden大小)
    * [Step 3:根据使用上限调整HBase配置](https://www.selinux.tech/java/core/pages/-LggfkvjQQpeojTzey2Q#step-3根据使用上限调整hbase配置)
    * [Step 4: 配置其他建议的参数以提高GC的性能](https://www.selinux.tech/java/core/pages/-LggfkvjQQpeojTzey2Q#step-4-配置其他建议的参数以提高gc的性能)
    * [Run it](/java/core/jvm-hbase#run-it)
  * [其他参考](https://www.selinux.tech/java/core/pages/-LggfkvjQQpeojTzey2Q#其他参考)

前面我们介绍过，G1GC 是现在比较流行的JVM 垃圾回收。所以，我们就结合G1GC 来讨论下Hadoop的GC。

## HBase的G1GC

首先，我们本篇文章的主要依据就是官方的blog。<https://blogs.apache.org/hbase/entry/tuning_g1gc_for_your_hbase>

鉴于已经有官方blog作为资料，并且实验环境并不是很充足，所以我们直接叙述结论。

## 参数配置

首先看下，我们实际工作中的HBase的参数配置。以及各项参数的实际含义。

| 参数                               | 含义                                                                                 |
| -------------------------------- | ---------------------------------------------------------------------------------- |
| -XX:+UseG1GC                     | 显式地要求使用G1 GC                                                                       |
| -Xms64G                          | 设置JVM初始堆内存为64G                                                                     |
| -Xmx64G                          | 设置JVM最大可用内存为64G                                                                    |
| -XX:PermSize=256m                | JVM初始分配的非堆内存                                                                       |
| -XX:G1NewSizePercent=4           | 新生代最小值，默认值5%                                                                       |
| -XX:MaxGCPauseMillis=200         | 设置G1收集过程目标时间，默认值200ms，不是硬性条件                                                       |
| -XX:ParallelGCThreads=16         | STW期间，并行GC线程数                                                                      |
| -XX:MaxTenuringThreshold=4       | 年龄阈值，默认15（对象被复制的次数）                                                                |
| -XX:+UnlockExperimentalVMOptions | 与+UseG1GC 一起使用,来解锁参数,应该是一种安全机制                                                     |
| -XX:+ParallelRefProcEnabled      | 默认为false，并行的处理Reference对象，如WeakReference，除非在GC log里出现Reference处理时间较长的日志，否则效果不会很明显。 |
| -XX:-ResizePLAB                  | 是否启动动态修改                                                                           |

## 如何调整HBase集群

### 开始之前: GC and HBase monitoring

可以使用任何工具跟踪收集 `block cache`, `memstore` 和 `static index size` 这些指标，可以从 `RegionServer JMX metrics` 中找到。

* “memStoreSize”
* “blockCacheSize”
* “staticIndexSize”

还可以使用官方的 `collectd` 插件来跟踪一段时间内的G1GC性能,并使用 `gc_log_visualizer`来了解特定的GC日志。要使用这些内容就要在 RegionServers 上跟踪这些信息。

* -Xloggc:$GC\_LOG\_PATH
* -verbosegc
* -XX:+PrintGC
* -XX:+PrintGCDateStamps
* -XX:+PrintAdaptiveSizePolicy
* -XX:+PrintGCDetails
* -XX:+PrintGCApplicationStoppedTime
* -XX:+PrintTenuringDistribution

还建议使用某种GC日志轮换，例如:

* -XX：+ UseGCLogFileRotation
* -XX：NumberOfGCLogFiles = 5
* -XX：GCLogFileSize = 20M

### Step 0：推荐默认值

建议将以下JVM参数和值作为HBase RegionServers的默认值:

* -XX：+ UseG1GC
* -XX：+ UnlockExperimentalVMOptions
* -XX：MaxGCPauseMillis = 50
* -XX：-OmitStackTraceInFastThrow
* -XX：ParallelGCThreads = 8+（逻辑处理器-8）
* -XX：+ ParallelRefProcEnabled
* -XX：+ PerfDisableSharedMem
* -XX：-ResizePLAB

### Step 1:确定预期的HBase最大使用率

使用上面提到的RegionServer JMX指标，查找整个集群中每个指标的最大值

* Maximum block cache size.
* Maximum memstore size.
* Maximum static index size.

将每个最大值扩展到 110%，以适应最大使用值的轻微增幅。这是该指标的使用上限: 例如最大10 GB记录的memstore→11 GB memstore上限.

理想情况下，在过去一周或一个月内跟踪这些指标，就可以找到该时间段内的最大值。如果不是，在计算 memstore和static index size 上限时要比110％ 更高一些。尤其是 Memstore，随着时间的推移，可能会变的更大。

### Step 2: 设置堆大小，IHOP和Eden大小

先从比较低的 Eden 大小开始设置：如果不是很确定，8% 的堆的大小就可以。

```java
-XX:G1NewSizePercent = 8
```

* 增加Eden大小会增加单个GC pause 时间，但会略微减少GC中花费的总时间。
* 减小Eden大小会产生相反的效果：GC pause 时间越短，GC中的总体时间略长。

用Step 1中的Eden大小和使用上限确定必要的堆大小:

Heap ≥ (M + B + O + E) ÷ 0.7

* M = memstore cap, GB
* B = block cache cap, GB
* O = static index cap, GB
* E = Eden size, GB

根据计算的值设置固定堆大小的JVM参数,例如：

```java
-Xms40960m -Xmx40960m
```

根据使用上限和堆大小在JVM中设置IHOP:

* IHOP = (memstore cap’s % heap + block cache cap’s % heap + overhead cap’s % heap + 20)
* -XX:InitiatingHeapOccupancyPercent = IHOP

### Step 3:根据使用上限调整HBase配置

根据使用上限和总堆大小,在 HBase 配置中设置 `block cache` 上限 和 `memstore` 上限比率:

* hfile.block.cache.size → block cache cap ÷ heap size
* hbase.regionserver.global.memstore.size → memstore cap ÷ heap size

### Step 4: 配置其他建议的参数以提高GC的性能

* -XX:MaxTenuringThreshold = 1
* -XX:G1HeapWastePercent = 10
* -XX:G1MixedGCCountTarget = 16
* -XX:G1HeapRegionSize = #M  #必须是2的幂，在\[1..32]范围内,理想情况下，＃是这样的：堆大小÷＃MB = 2048个region。

### Run it

应用这些配置，然后重启RegionServers，看看他们的表现。

* 可以如上所述调整Eden大小，以优化较短的单个GC或减少GC中的总时间,如果这样做，请确保Eden +IHOP≤90％。
* 如果HBase客户端可能有非常突发的流量，可以考虑在IHOP和Eden之外添加堆空间（例如，IHOP + Eden加起来高达80％.

## 其他参考

* [G1GC Fundamentals (HubSpot blog)](https://product.hubspot.com/blog/g1gc-fundamentals-lessons-from-taming-garbage-collection)
* [Understanding G1GC Logs (Oracle blog)](https://blogs.oracle.com/poonam/entry/understanding_g1_gc_logs)
* [Tuning HBase Garbage Collection (Intel blog)](https://blogs.oracle.com/poonam/understanding-g1-gc-logs)


# JVM ZGC Overview

* [ZGC 概览](#zgc-概览)
  * [Features](#features)
    * [不足](#不足)
  * [支持的平台](#支持的平台)
  * [配置和调优](#配置和调优)
    * [常见参数](#常见参数)
  * [启用ZGC](#启用zgc)
  * [设置堆大小](#设置堆大小)
  * [设置并发 GC 线程](#设置并发-gc-线程)
  * [将未使用的内存返回给操作系统](#将未使用的内存返回给操作系统)
  * [在 Linux 上启用大页面](#在-linux-上启用大页面)
  * [在Linux上启用透明的巨大页面](#在linux上启用透明的巨大页面)
  * [启用NUMA支持](#启用numa支持)
  * [启用 GC 日志记录](#启用-gc-日志记录)
  * [迭代日志](#迭代日志)
    * [JDK 17](#jdk-17)
    * [JDK 16](#jdk-16)
    * [JDK 15](#jdk-15)
    * [JDK 14](#jdk-14)
    * [JDK 13](#jdk-13)
    * [JDK 12](#jdk-12)
    * [JDK 11](#jdk-11)
  * [FAQ](#faq)
    * [ZGC中的 "Z "代表什么？](#zgc中的-z-代表什么)
    * [它的发音是 "zed gee see "还是 "zee gee see"？](#它的发音是-zed-gee-see-还是-zee-gee-see)
  * [参考文献](#参考文献)

Z Garbage Collector，也称为ZGC，是一种可扩展的低延迟垃圾收集器，旨在满足以下目标：

* 最大暂停时间在亚毫秒级
* 暂停时间不会随着堆、live-set 或 root-set 的大小而增加
* 处理大小从8MB到16TB的堆
* ZGC 最初是作为 JDK 11 中的一项实验性功能引入的，并在 JDK 15 中被宣布为Production Ready。

ZGC 的核心是一个并发垃圾收集器，这意味着所有繁重的工作都在Java 线程继续执行的同时完成。这极大地限制了垃圾收集对应用程序响应时间的影响。

这个OpenJDK项目由HotSpot Group赞助。

## Features

* 不分代的垃圾回收器，即垃圾回收时对全量内存进行标记，但是回收时仅针对分内存回收，优先回收垃圾比较多的页面。
* 仅支持Linux64位系统，不支持32位平台。
* 不支持使用压缩指针。
* 内存分区管理，且支持不同的分区粒度，在ZGC中分区称为页面（page),有小页面、中页面、大页面3种。
* 具有颜色指针（color pointer),通过设计不同的标记位区分不同的虚拟空间，而这些不同标记位指示的不同虚拟空间通过mmap映射在同一物理地址；颜色指针能够快速实现并发标记、转移和重定位。
* 设计了读屏障，实现了并发标记和并发转移的处理。
* 支持NUMA，尽量把对象分配在访问速度比较快的地方。

### 不足

* 仅实现了单代内存管理，也就是说没有考虑热点数据与冷数据，分代内存管理在C4中已经得到支持。据Azul官网文章介绍，所实现的分代的内存管理器比没有分代的内存管理器效率高10倍，也就是说ZGC还有巨大的进步空间。
* C2的支持还不够完善。
* 不支持Graal、HDSB等功能。
* 一些功能尚待完善，比如尚不支持类回收。
* 稳定性尚需提高。

## 支持的平台

| platform        | Since                   |
| --------------- | ----------------------- |
| Linux/x64       | JDK 15（自 JDK 11 起是实验性的） |
| Linux/AArch64   | JDK 15（自 JDK 13 起实验性）   |
| Linux/PPC       | JDK 17                  |
| macOS/x64       | JDK 15（自 JDK 14 以来的实验性版 |
| macOS/AArch64   | JDK 17                  |
| Windows/x64     | JDK 15（自 JDK 14 以来的实验性版 |
| Windows/AArch64 | JDK 16                  |

## 配置和调优

### 常见参数

**通用的GC 选项**

* -XX:MinHeapSize, -Xms
* -XX:InitialHeapSize, -Xms |-XX:ZCollectionInterval
* -XX:MaxHeapSize, -Xmx |-XX:ZFragmentationLimit
* -XX:SoftMaxHeapSize
* -XX:ConcGCThreads
* -XX:ParallelGCThreads
* -XX:UseDynamicNumberOfGCThreads
* -XX:UseLargePages
* -XX:UseTransparentHugePages
* -XX:UseNUMA
* -XX:SoftRefLRUPolicyMSPerMB
* -XX:AllocateHeapAt

**ZGC选项**

* -XX:ZAllocationSpikeTolerance
* -XX:ZAllocationSpikeTolerance
* -XX:ZMarkStackSpaceLimit
* -XX:ZProactive
* -XX:ZUncommit
* -XX:ZUncommitDelay

**ZGC 诊断选项（-XX:+UnlockDiagnosticVMOptions)**

* -XX:ZStatisticsInterval
* -XX:ZVerifyForwarding
* -XX:ZVerifyMarking
* -XX:ZVerifyObjects
* -XX:ZVerifyRoots
* -XX:ZVerifyViews

## 启用ZGC

-XX:+UseZGC

## 设置堆大小

ZGC 最重要的调优选项是设置最大堆大小 ( -Xmx)。由于 ZGC 是并发收集器，因此必须选择最大堆大小，以便

* 堆可以容纳应用程序的实时数据集
* 堆中有足够的空间来允许在 GC 进行时处理分配程序。

需要多大的余量在很大程度上取决于应用程序的分配率和实时集的大小。一般来说，你给ZGC的内存越多越好。但同时，浪费内存也是不可取的，所以关键是要在内存使用和GC需要运行的频率之间找到一个平衡。

## 设置并发 GC 线程

第二个调整选项是设置并发的GC线程数量（-XX:ConcGCThreads=）。ZGC有启发式方法来自动选择这个数字。这种启发式方法通常工作得很好，但根据应用程序的特点，可能需要调整。这个选项本质上决定了应该给GC多少CPU时间。给它太多，GC会从应用中窃取过多的CPU时间。给得太少，应用程序分配垃圾的速度可能比GC收集垃圾的速度快。

**注意**！一般来说，如果低延迟（即低应用响应时间）对你的应用很重要，那么永远不要过度配置你的系统。理想情况下，你的系统的CPU利用率不应超过70%。

## 将未使用的内存返回给操作系统

默认情况下，ZGC不提交未使用的内存，并将其返回给操作系统。这对于那些需要考虑内存占用的应用程序和环境是很有用的。这个功能可以用-XX:-ZUncommit来禁用。此外，内存不会被取消提交，这样堆的大小就会缩小到最小堆大小（-Xms）以下。这意味着如果最小堆大小（-Xms）被配置为等于最大堆大小（-Xmx），这个功能将被隐式禁用。

可以用-XX:ZUncommitDelay=（默认是300秒）来配置未提交延迟。这个延迟指定了内存在多长时间内未被使用才有资格被解密。

**注意** ！在Linux上，取消提交未使用的内存需要fallocate(2)支持FALLOC\_FL\_PUNCH\_HOLE，它首次出现在内核版本3.5（针对tmpfs）和4.3（针对hugetlbfs）。

## 在 Linux 上启用大页面

将ZGC配置为使用大页面通常会产生更好的性能（在吞吐量、延迟和启动时间方面），并且没有真正的缺点，只是它的设置稍微复杂一些。设置过程通常需要root权限，这就是为什么它在默认情况下不启用。

在Linux/x86上，大页面（也被称为 "巨大页面"）的大小为2MB。

让我们假设你想要一个16G的Java堆。这意味着你需要16G / 2M = 8192个巨大的页面。

首先将至少16G（8192页）的内存分配给巨大页池。"至少 "这个部分很重要，因为在JVM中启用大页面意味着不仅GC会尝试将这些页面用于Java堆，而且JVM的其他部分也会尝试将其用于各种内部数据结构（代码堆、标记位图等）。因此，在这个例子中，我们将保留9216个页面（18G），允许2G的非Java堆分配使用大页面。

配置系统的巨大页面池，使其拥有所需的页面数量（需要root权限）。

```
echo 9216 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
```

注意，如果内核不能找到足够的空闲的巨大页面来满足请求，上述命令不保证成功。还要注意，内核可能需要一些时间来处理这个请求。在继续之前，检查分配给池子的巨大页面的数量，以确保请求是成功的并且已经完成。

```
$ cat /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
9216 
```

注意！如果你使用的是>=4.14的Linux内核，那么接下来的步骤（挂载hugetlbfs文件系统）可以跳过。然而，如果你使用的是旧内核，那么ZGC需要通过hugetlbfs文件系统来访问大的页面。

挂载一个hugetlbfs文件系统（需要root权限），并使运行JVM的用户能够访问它（在这个例子中，我们假设这个用户的uid是123）。

```
$ mkdir /hugepages
$ mount -t hugetlbfs -o uid=123 nodev /hugepages 
```

现在使用-XX:+UseLargePages选项启动JVM。

```
$ java -XX:+UseZGC -Xms16G -Xmx16G -XX:+UseLargePages ...
```

如果有多个可访问的hugetlbfs文件系统可用，那么（也只有这时）你还必须使用-XX:AllocateHeapAt来指定你想使用的文件系统的路径。例如，假设有多个可访问的hugetlbfs文件系统被挂载，但你特别想使用的文件系统被挂载在/hugepages，那么使用以下选项。

```
$ java -XX:+UseZGC -Xms16G -Xmx16G -XX:+UseLargePages -XX:AllocateHeapAt=/hugepages ...
```

**注意** ！除非采取适当的措施，否则巨大的页面池的配置和hugetlbfs文件系统的挂载在重启后是不持久的。

## 在Linux上启用透明的巨大页面

使用显式大页面（如上所述）的一个替代方法是使用透明的大页面。对于延迟敏感的应用，通常不推荐使用透明的大页面，因为它往往会导致不必要的延迟峰值。然而，这可能值得试验一下，看看你的工作负载是否/如何受到它的影响。但请注意，你的里程可能会有所不同。

注意！在Linux上，使用ZGC并启用透明的巨大页面需要内核>=4.7。

使用以下选项在虚拟机中启用透明的巨大页面。

```
-XX:+UseLargePages -XX:+UseTransparentHugePages
```

这些选项告诉JVM为其映射的内存发出madvise(..., MADV\_HUGEPAGE)调用，这在madvise模式下使用透明的巨大页面时很有用。

为了启用透明的巨大页面，你还需要配置内核，启用madvise模式。

```
$ echo madvise > /sys/kernel/mm/transparent_hugepage/enabled
```

和

```
$ echo advise > /sys/kernel/mm/transparent_hugepage/shmem_enabled
```

## 启用NUMA支持

ZGC支持NUMA，这意味着它将尽力把Java堆分配到NUMA的本地内存。这个功能默认是启用的。然而，如果JVM检测到它只能使用单个NUMA节点的内存，它将自动被禁用。一般来说，你不需要担心这个设置，但如果你想明确地覆盖JVM的决定，你可以通过使用-XX:+UseNUMA或-XX:-UseNUMA选项来实现。

## 启用 GC 日志记录

GC日志是通过以下命令行选项启用的。

```
-Xlog:<tag set>,[<tag set>, ...]:<log file>
```

关于这个选项的一般信息/帮助。

`-Xlog:help`

启用基本的日志记录（每个GC有一行输出）。

`-Xlog:gc:gc.log`

启用对调整/性能分析有用的GC日志。

`-Xlog:gc*:gc.log`

其中gc\*意味着记录所有包含gc标签的标签组合，而:gc.log意味着将日志写入一个名为gc.log的文件。

## 迭代日志

### JDK 17

* 动态的GC线程数量
* 减少了标记堆栈内存的使用
* 支持macOS/aarch64
* 暂停和循环的GarbageCollectorMXBeans
* 快速的JVM终止

### JDK 16

* 并发线程堆栈扫描（JEP 376）
* 支持原地重定位
* 性能改进（转发表的分配/初始化等）。

### JDK 15

* 生产就绪 (JEP 377)
* 改进了NUMA意识
* 改进了分配并发性
* 支持类数据共享（CDS）
* 支持将堆放在NVRAM上
* 支持压缩的类指针
* 支持增量不提交
* 修复了对透明的巨大页面的支持
* 额外的JFR事件

### JDK 14

* 支持macOS (JEP 364)
* 支持Windows（JEP 365）
* 支持小/小堆（低至8M）。
* 支持JFR泄漏分析器
* 支持有限和不连续的地址空间
* 并行预触摸（当使用-XX:+AlwaysPreTouch）。
* 性能改进（克隆本征，等等）。
* 稳定性改进

### JDK 13

* 将最大堆大小从4TB增加到16TB
* 支持不提交未使用的内存（JEP 351）。
* 支持 -XX:SoftMaxHeapSIze
* 对Linux/AArch64平台的支持
* 减少了安全时间（Time-To-Safepoint

### JDK 12

* 支持并发的类卸载
* 进一步减少了暂停时间

### JDK 11

* ZGC的初始版本
* 不支持类卸载（使用 -XX:+ClassUnloading 无效）。

## FAQ

### ZGC中的 "Z "代表什么？

它并不代表什么，ZGC只是一个名字。它最初是受ZFS（文件系统）的启发，或者说是对它的致敬，ZFS在很多方面都是革命性的。最初，ZFS是 "Zettabyte File System "的首字母缩写，但这个意思被放弃了，后来据说它不代表任何东西。它只是一个名字而已。

### 它的发音是 "zed gee see "还是 "zee gee see"？

没有首选的发音，两种都可以。

## 参考文献

* [OpenJDK Wiki Main](https://wiki.openjdk.java.net/display/zgc/Main)
* 《新一代垃圾回收器-ZGC设计与实现》-- 彭成寒


# JVM ZGC 内存管理

* [ZGC 内存管理](#zgc-内存管理)
  * [ZGC 内存管理](#zgc-内存管理)
    * [页面设计](#页面设计)
  * [ZGC对象分配管理](#zgc对象分配管理)
    * [对象空间分配](#对象空间分配)
    * [页面分配](#页面分配)
  * [参考文献](#参考文献)

对象的分配直接关系到内存的使用效率、垃圾回收的效率，不同的分配策略也会影响对象的分配速度，从而影响应用程序的运行。 ZGC为了支持太字节（TB)级内存，设计了基于页面（page)的分页管理（类似于G1的分区Region);为了能够快速地进行并发标记和并发移动，对内存空间重新进行了划分，这就是ZGC中新引入的Color Pointers;同时ZGC为了能更加高效地管理内存，设计了物理内存和虚拟内存两级内存管理。

## ZGC 内存管理

ZGC为了能高效、灵活地管理内存，实现了两级内存管理：虚拟内存和物理内存，并且实现了物理内存和虚拟内存的映射关系。这和操作系统中虚拟地址和物理地址设计思路基本一致。ZGC主要的改进点就是重新定义了虚拟内存和物理内存的映射关系。

我们先从一个问题出发。我们知道ZGC目前**仅支持64位Linux,最多管理4TB**的内存。但是我们知道，64位系统支持的内存远超过4TB,那么为什么我们一直强调它只能支持4TB的内存，为什么不使用更多的虚拟内存？

```
+--------------------------------+ 0x00007FFFFFFFFFFF (127TB)
-                                -
-                                -
-                                -
+--------------------------------+ 0x0000140000000000 (20TB)
| Remapped View                  |
+--------------------------------+ 0x0000100000000000 (16TB)
| (Reserved, but unused)         |
+--------------------------------+ 0x00000c0000000000 (12TB)
| Marked1 View                   |
+--------------------------------+ 0x0000080000000000 (8TB)
| Marked0 View                   |
+--------------------------------+ 0x0000040000000000 (4TB)

+--------------------------------+ 0x0000000000000000 
```

上面的示意图中提到了3个视图，分别是Marked0、Marked1和Remapped,这3个视图会映射到操作系统的同一物理地址。这就是ZGC中Color Pointers的概念，通过Color Pointers来区别不同的虚拟视图。 在ZGC中常见的几个虚拟空间有\[0 \~ 4TB)、\[4TB \~ 8TB)、\[8TB \~ 12TB)和\[16TB \~ 20TB)。其中\[0 \~ 4TB)对应的是Java的堆空间；\[4TB \~ 8TB)、\[8TB \~ 12TB)和\[16TB \~ 20TB)分别对应Marked0、Marked1和Remapped这3个视图。 这三个视图的关系，如下所示。

![ZGC中虚拟地址与物理地址的映射关系](/files/Z8Vik0lK7SZTDnCz6HyU)

* 4TB是理论上最大的堆空间，其大小受限于JVM参数。
* 0 \~ 4TB的虚拟地址是ZGC提供给应用程序使用的虚拟空间，它并不会映射正的物理地址。
* 操作系统管理的虚拟内存为Marked0、Marked1和Remapped这3个空间，且对应同一物理空间。
* 在ZGC中这3个空间在同一时间点有且仅有一个空间有效。为什么这么设计就是ZGC的高明之处，利用虚拟空间换时间；
* 应用程序可见并使用的虚拟地址为0 \~ 4TB,经ZGC转化，真正使用的虚址为\[4TB \~ 8TB)、\[8TB \~ 12TB)和\[16TB \~ 20TB),操作系统管理的虚拟也是\[4TB \~ 8TB)、\[8TB \~ 12TB)和\[16TB \~ 20TB)。应用程序可见的虚拟\[0 \~ 4TB)和物理内存直接的关联由ZGC来管理。

ZGC支持64位系统，我们看一下ZGC是如何使用64位地址的。ZGC中低42位（第0 \~ 41位）用于描述真正的虚拟地址（这就是图2-2中提到的应用程序可以使用的堆空间）,接着的4位（第42 \~ 45位）用于描述元数据，其实就是大家所说的Color Pointers,还有1位（第46位）目前暂时没有使用，最高17位（第47 \~ 63位）固定为0,具体如下所示。

```
 6                 4 4 4  4 4                                             0
 3                 7 6 5  2 1                                             0
+-------------------+-+----+-----------------------------------------------+
|00000000 00000000 0|0|1111|11 11111111 11111111 11111111 11111111 11111111|
+-------------------+-+----+-----------------------------------------------+
|                   | |    |
|                   | |    * 41-0 Object Offset (42-bits, 4TB address space)
|                   | |
|                   | * 45-42 Metadata Bits (4-bits)  0001 = Marked0
|                   |                                 0010 = Marked1
|                   |                                 0100 = Remapped
|                   |                                 1000 = Finalizable
|                   |
|                   * 46-46 Unused (1-bit, always zero)
|
* 63-47 Fixed (17-bits, always zero)
```

由于42位地址最大的寻址空间就是4TB,这就是ZGC一直宣称自己最大支持4TB内存的原因。这里还有视图的概念，Marked0、Marked1和Remapped就是3个视图，分别将第42、43、44位设置为1,就表示采用对应的视图。在ZGC中，这4位标记位的目的并不是用于地址寻址的，而是为了区分Marked0、Marked1和Remapped这3个视图。当然对于操作系统来说，这4位标记位代表了不同的虚拟地址空间，操作系统在寻址的时候会把标记位和虚拟地址结合使用。

### 页面设计

为了细粒度地控制内存的分配，和G1一样，ZGC将内存划分成小的分区，在ZGC中称为页面（page)。ZGC支持3种页面，分别为小页面、中页面和大页面。其中小页面指的是2MB的页面空间，中页面指32MB的页面空间，大页面指受操作系统控制的大页。

那操作系统所支持的大页是什么呢？

标准大页（huge page)是Linux Kemel2.6引入的，目的是通过使用大页内存来取代传统的4KB内存页面，以适应越来越大的系统内存，让操作系统可以支持现代硬件架构的大页面容量功能。它有两种大小：2MB和1GB。2MB页块大小适合用于G字节级的内存，1GB页块大小适合用于T字节级别的内存；2MB是默认的大页尺寸。在ZGC中还支持透明大页（Transparent Huge Pages,THP),这是RHEL6开始引入的一个功能，在Linux6上THP是默认启用的。由于设置大页比较麻烦，很难手动管理，而且通常需要对代码进行重大的更改才能有效地使用，因此RHEL6中开始引入了THP,它是一个抽象层，能够自动创建、管理和使用传统大页。

在ZGC中，不同对象的大小会使用不同的页面类型。下面的表格中列出了ZGC页面大小对象大小和对象对齐数据。 MinObjectAlignmentlnBytes的默认值是8,它由参数ObjectAlignmentIn-Bytes控制，大小在8 \~ 256之间，且为2的寡次。对象对齐的粒度影响的是对象的分配和访问速度以及内存空间的浪费，通常来说，粒度越大，处理器访问内存的速度越快，但可能导致过多的浪费。在实际中可以根据应用系统对象的平均大小来合理地设置该值。

| 类型  | 页面大小          | 页面内对象的大小     | 页面内对象对齐的粒度                |
| --- | ------------- | ------------ | ------------------------- |
| 小页面 | 2MB           | 小于等于256KB    | MinObjectAlignmentInBytes |
| 中页面 | 32MB          | 在256KB和4MB之间 | 4KB                       |
| 大页面 | X\*MB,受操作系统控制 | 大于4MB        | 2MB                       |

## ZGC对象分配管理

### 对象空间分配

![对象空间分配流程图](/files/LCVqfTMX7gO4c5qtaLgF)

这里稍微做一下解释：

* 分配小对象时，会判断对象的请求来自哪里。如果来自于应用线程，所有的应用线程根据所在的CPU从共享的页面中分配对象空间。如果请求来自工作线程，则每个工作线程都有一个缓存的页面，优先从缓存的页面分配，不成功则分配新的页面。这样设计的主要目的在于使工作线程分配对象只发生在对象的并发转移中，多个缓存能加快对象的转移。
* 分配中等对象的时候，所有的中等对象都共享一个中等页面，也就是说该函数会被并发访问（可能是工作线程和应用程序线程并发执行，也有可能是多个应用程序线程之间并发执行）,所以涉及竞争，需要额外的处理。ZGC中是先分配页面空间，再尝试设置新页面为共享页面，在设置过程中需要原子操作（通常在一个循环中处理）,如果不能成功设置，说明有多个线程并发执行，且有其他的线程已经成功申请到新的页面，此时要释放多申请的页面。从这里可以看出，如果应用程序中含有大量中等对象，ZGC在空间分配时很容易发生页面申请竞争，导致性能下降。
* 对于大对象来说，ZGC不会在一个大页面中共享多个大对象，也就是说每个大对象都独占一个大页面，当然大页面的大小可能不相同（主要取决于对象的大小）。
* 还有一点，在上述的流程图中并没有体现，在ZGC中有一个参数ZStallOnOut-OfMemory,用于控制当发生OOM时终止程序还是等待垃圾回收器回收空间后继续运行。该参数在页面分配时使用，

### 页面分配

![一般页面的分配流程](/files/o9Ktno39raFTb5UsIq3Z)

## 参考文献

* 《新一代垃圾回收器-ZGC设计与实现》-- 彭成寒


# JVM ZGC 线程

* [ZGC 线程](#zgc-线程)
  * [控制线程](#控制线程)
    * [时钟触发器 -- ZDirector 的触发流程](#时钟触发器----zdirector-的触发流程)
    * [时钟触发器 -- ZStat的统计流程](#时钟触发器----zstat的统计流程)
    * [消息触发 -- ZDriver](#消息触发----zdriver)
    * [VMThread（TODO）](#vmthreadtodo)
  * [工作线程 （TODO）](#工作线程-todo)
  * [垃圾回收触发的时机](#垃圾回收触发的时机)
    * [1. 基于固定时间间隔触发](#1-基于固定时间间隔触发)
    * [2. 预热规则触发](#2-预热规则触发)
    * [3. 根据分配速率](#3-根据分配速率)
    * [4. 主动触发](#4-主动触发)
    * [5. 阻塞内存分配请求触发](#5-阻塞内存分配请求触发)
    * [6. 外部触发](#6-外部触发)
    * [7. 元数据分配触发](#7-元数据分配触发)

![线程类结构图](/files/73V9P5H19p6OcVpcczz6)

**ZGC 中涉及到了下面几类线程**

* JavaThread:要执行的Java代码的线程，比如一个Java代码启动后会变成一个JavaThread运行；对于Java代码的启动线程，通过JNI\_CreateJavaVM来创建一个JavaThread,而对于一般的Java线程，都是调用java.lang.thread里面的start方法，这个方法通过JNI调用创建JavaThread对象，完成真正的线程创建。
* CompilerThread:执行JIT的线程。
* NameThread:是JVM内部使用的线程。
* VMThread:JVM执行垃圾回收的同步线程，是JVM最关键的线程之一，主要的用途之一是处理垃圾回收。简单地说，所有的垃圾回收操作都是从VMThread触发的，如果是多线程回收，则启动多个线程，如果是单线程回收，则使用VMThread进行。VMThread提供了一个队列（queue),任何要执行垃圾回收的操作都实现了VM\_GC\_Operation,在JavaThread中执行VMThread:execute(VM\_GC\_Operation)把垃圾回收操作放入队列中，然后在VMThread的run方法中轮询这个队列就可以了。当这个队列有内容时，它就开始尝试进入安全点，然后执行相应的垃圾回收任务，完成垃圾回收任务后会退出安全点。
* ConcurrentGCThread:并发执行垃圾回收任务的线程，控制线程ZDirector、ZDriver和ZStat都继承于该线程，实现并发执行。
* Gang Worker:工作线程，在ZGC中ZWorkers就包含了多个GangWorker,这个线程是并行执行的（个数一般和CPU个数相关）,所以可以认为这是一个线程池。线程池里面的线程用于执行任务（如执行ZMarkRootsTask、ZMarkTask等任务）,进行垃圾回收。

## 控制线程

* ZDirector：控制什么时候启动垃圾回收
* ZDriver：控制垃圾回收执行的步骤，ZGC垃圾回收一共分10步，串行执行。
* ZStat：收集JVM在运行过程中回收垃圾时各个阶段的数据，同时控制统计信息的输出。

### 时钟触发器 -- ZDirector 的触发流程

ZDirector 和 ZStat 都是通过时钟触发器来判断是否执行业务。

ZDirector 提供了4种触发垃圾回收的方法，分别是基于固定时间间隔，预热规则，分配速率和主动触发规则。ZDirector依次按照优先级由高到低判断4种规则是否满足。

![ZDirector 流程图](/files/rEYBNtyNiwdsBLSdksYD)

ZDirector 虽然代码实现为并发线程，但在ZGC中只有一个，所ZDirector不会出现并发的问题。

### 时钟触发器 -- ZStat的统计流程

统计线程ZStat为每1s收集信息一次。但是在输出时统计线程会把收集到的数据进行聚合，目前提供了3种粒度的统计数据，分别为过去10s,过去10min和过去10h的统计数据。这3个粒度的数据可以定义为：统计线程最近10次运行的数据，60个过去10s的数据和60个过去10min的数据，所以实际需要存储的只有130个数据。

### 消息触发 -- ZDriver

在进行消息处理时，ZGC设计了两种消息处理方式：同步垃圾回收和异步垃圾回收。同步垃圾回收主要是保证垃圾回收一定会发生，并且直到垃圾回收完成才会继续执行；异步垃圾回收则是为了实现更高的吞吐量，如果有多个异步消息在同一垃圾回收周期到达，则只有一个请求被处理，即垃圾回收只会执行一次。

ZGC中触发垃圾回收的只要是由ZDirector产生的异步消息。

![ZDriver流程图](/files/KT3CxY2ynQ9BCnRZGXlR)

### VMThread（TODO）

## 工作线程 （TODO）

## 垃圾回收触发的时机

ZGC中，为了实现更高的性能，尽量避免进行同步垃圾回收，也就是说尽量避免发同步垃圾回收的消息。ZGC中触发同步消息的场景也比较少，总体以触发异步消息为主。异步消息主要由ZDirector根据规则判断是否可以触发，在ZDirector流程图中介绍了ZDirector有4种触发规则，主要介绍这4种规则是如何触发的，最后还会简要介绍其他的垃圾回收消息是如何触发的。

### 1.基于固定时间间隔触发

ZDirector提供的第一个规则就是基于固定时间间隔触发垃圾回收。这个规则的目的非常简单，就是希望ZGC的垃圾回收器以固定的频率触发。在这一些场景中非常有用,例如我们的应用程序在晚上请求量比较低的情况下运行了很长时间，但是ZGC不满足其他垃圾回收器的触发条件，所以一直不会触发垃圾回收，这通常没什么问题，如果在早上某一个时间点开始请求暴增，这可能导致内存使用也暴增，而垃圾回收器来不及回收垃圾对象，将降低应用系统的吞吐量。所以ZGC提供了基于固定时间间隔触发垃圾回收的规则。

这个规则的实现也非常简单，就是判断前一次垃圾回收结束到当前时间是否超过时间间隔的罔值，如果超过，则触发垃圾回收，如果不满足，则直接返回。 需要说明的是，时间间隔由一个参数ZCollectionInterval来控制，这个参数的默认值为0,表示不需要触发垃圾回收。 实际工作中，可以根据场景设置该参数。

### 2.预热规则触发

ZDirector提供的第二个规则是预热启动垃圾回收。为什么设计这一规则？设计这一规则的目的是当JVM刚启动时，还没有足够的数据来主动触发垃圾回收的启动，所以设置了预热规则。 预热规则指的是JVM启动后，当发现堆空间使用率达到10%、20%和30%时，主动地触发垃圾回收。ZGC设计前3次垃圾回收可由预热规则触发，也就是说当垃圾回收触发（无论是由预热规则，还是主动触发垃圾回收）的次数超过3次时，预执规再生效。

### 3.根据分配速率

ZDirector提供的第二个规则是根据分配速率来判断是否能触发垃圾回收。

**设计规则TODO**

### 4. 主动触发

ZDirector提供的第四个规则是主动触发规则，该规则是为了应用程序在吞吐量下降的情况下，当满足一定条件时，还可以执行垃圾回收。这里满足一定条件指的是：

* 1\)从上一次垃圾回收完成到当前时间，应用程序新增使用的内存达到堆空间的10%。
* 2\)从上一次垃圾回收完成到当前时间已经过去了5min,记为timeelapsed。

如果这两个条件同时满足，预测垃圾回收时间为timegc,定义规则：如果 numgc \* timegc < timeelapsed,则触发垃圾回收。其中numgc是ZGC设计的常量，假设应用程序的吞吐率从50%下降到1%,需要触发一次垃圾回收。 这个规则实际上是为了弥补程序吞吐率骤降且长时间不执行垃圾回收而引入的。有一个诊断参数 ZProactive来控制是否开启和关闭主动规则，默认值是true,即默认打开主动触发规则。 实际上这个规则和第一个规则（基于固定时间间隔规则）在某些场景中有一定的重复，第一个规则只强调时间间隔，本规则除了考虑时间之外，还会考虑内存的增长和吞吐率下降的快慢程度。

### 5.阻塞内存分配请求触发

阻塞内存分配由参数ZStallOnOutOfMemory控制，当参数ZStallOnOutOfMemory为true时进行阻塞分配，如果不能成功分配内存，则触发阻塞内存分配。

### 6.外部触发

外部触发是指在Java代码中显式地调用System.gc()函数，在JVM执行该函数时，会触发垃圾回收。该触发请求是从用户代码主动触发的，从编程角度来看，说明程序员认为此时需要进行垃圾回收（当然首先是程序员正确使用System.gc()函数）,所以ZGC把该触发规则设计为同步请求，只有在执行完垃圾回收后，才能进行后续代码的执行。

### 7.元数据分配触发

元数据分配失败时，ZGC会尝试进行垃圾回收以确保元数据能正确分配。 异步垃圾回收后会尝试是否可以分配元数据对象空间，如果不能，将尝试进行同步垃圾回后可以分配元数据对象空间，如果还不成功，则尝试扩展元数据空间，再分配成功则返回内存空间，不成功则返回NULL。


# JVM ZGC 垃圾回收


# JVM ZGC 日志分析


# JVM ZGC 参数调优

* [ZGC 参数调优](#zgc-参数调优)
  * [ZGC 新引入的参数](#zgc-新引入的参数)

## ZGC 新引入的参数

| 类型   | 参数                              | 默认值   | 描述                                                                  |
| ---- | ------------------------------- | ----- | ------------------------------------------------------------------- |
| 生产选项 | ZPath                           | null  | 指定堆空间存储使用的文件系统，仅支持tmpfs或者hugelbfs                                   |
| 生产选项 | ZAllocationSpikeTolerance       | 2     | 垃圾回收分配速率触发的修正预测参数，该参数越大，垃圾回收执行得越频繁                                  |
| 生产选项 | ZFragmentationLimit             | 25    | "指定页面中在垃圾回收期间允许存在的垃圾的最大比例。在垃圾回收期间，当页面中的垃圾空间超过该比例，则页面会进入回收，否则页面不会被回收 |
| "    |                                 |       |                                                                     |
| 生产选项 | ZStallOnOutOfMemory             | TRUE  | 指定JVM在内存不足时等待垃圾回收完成，而不是抛出OOM                                        |
| 生产选项 | ZMarkStacksMax                  | 8GB   | 标记栈的最大空间，标记过程中如果标记栈的空间达到该值，则JVM会直接退出                                |
| 生产选项 | ZCollectionInterval             | 0     | 允许自定义执行垃圾回收的间隔，0表示不执行自定义触发的规则                                       |
| 生产选项 | ZStatisticsInterval             | 10    | 输出统计信息的时间间隔                                                         |
| 诊断选项 | ZStatisticsForceTrace           | FALSE | 统计信息详情                                                              |
| 诊断选项 | ZProactive                      | TRUE  | 主动触发垃圾回收                                                            |
| 诊断选项 | ZUnmapBadViews                  | FALSE | 仅把当前有效的地址映射到地址视图中                                                   |
| 诊断选项 | ZVerifyMarking                  | FALSE | 在标记开始和标记结束时验证标记栈，应该为空                                               |
| 诊断选项 | ZVerifyForwarding               | FALSE | 在并发转移中验证转移表（forwarding)没有重复的对象                                      |
| 诊断选项 | ZSymbolTableUnloading           | FALSE | 在第3步中是否卸载未使用的符号表                                                    |
| 诊断选项 | ZWeakRoots                      | TRUE  | 弱根集合回收，为true时在垃圾回收周期的第3步中回收，为false时在垃圾回收周期的第1步中回收                   |
| 诊断选项 | ZConcurrentStringTable          | TRUE  | 允许并发回收字符串表，为true时在垃圾回收周期的第4步中回收，为false时在垃圾回收周期的第3步中回收               |
| 诊断选项 | ZConcurrentVMWeakHandles        | TRUE  | 允许并发回收VM弱句柄，为true时在垃圾回收周期的第4步中回收，为false时在垃圾回收周期的第3步中回收              |
| 诊断选项 | ZConcurrentJNIWeakGlobalHandles | TRUE  | 允许并发回收JNI弱句柄，为true时在垃圾回收周期的第4步中回收，为false时在垃圾回收周期的第3步中回收             |
| 诊断选项 | ZOptimizeLoadBarriers           | TRUE  | 允许C2中对读屏障进行优化，优化的点主要根据数据流优化                                         |
| 开发选项 | ZVerifyLoadBarriers             | FALSE | 在C2中涉及读屏障时，会对读屏障进行验证                                                |


# checkstyle

## 现装介绍

目前 使用的CheckStyle 版本是checkstyle-6.16.1-all.jar。

目前的CheckStyle的验证方式是 通过 git hook 的方式来进行，验证。即，在push 代码的时候，通过一段shell 脚本来验证 本次变更中 哪些Java代码是 不合规范的。

**Each module has a severity property that a Checkstyle audit assigns to all violations of the check. The default severity level of a check is error.**

## 旧的CheckStyle 梳理

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.3//EN" "http://www.puppycrawl.com/dtds/configuration_1_3.dtd">
 
<!--
    Checkstyle-Configuration: easyop-cs-2.0
    Description: easyop checkstyle rule 2.0, since 2018-02-01
-->
<module name="Checker">
    <property name="severity" value="error"/>
    <module name="TreeWalker">
        <!-- 当配置为 TreeWalker 子模块时，保存当前文件内容以供全局访问。例如，过滤器可以通过此模块访问当前文件内容。 -->
        <module name="FileContentsHolder"/>
        <!-- 参数分配通常被认为是糟糕的编程实践。强制开发人员将参数声明为 final 通常是繁重的。检查确保永远不会分配参数将两全其美。 -->
        <module name="ParameterAssignment">
            <property name="severity" value="error"/>
            <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="error"/>
        </module>
        <!-- 不许使用未被简化的条件表达式 -->
        <module name="SimplifyBooleanExpression">
            <property name="severity" value="error"/>
            <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="error"/>
        </module>
        <!-- 检查静态的非final变量名是否符合指定的模式，默认"^[a-z][a-zA-Z0-9]*$" -->
        <module name="StaticVariableName">
            <property name="severity" value="error"/>
        </module>
        <!-- 检查方法名称是否符合指定的模式，默认"^[a-z][a-zA-Z0-9]*$" -->
        <module name="MethodName">
            <property name="severity" value="error"/>
        </module>
        <!-- 不许内部赋值 String s = Integer.toString(i = 2) -->
        <module name="InnerAssignment">
            <property name="severity" value="error"/>
        </module>
        <!-- 检查未使用的导入包 -->
        <module name="UnusedImports">
            <property name="severity" value="error"/>
            <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="error"/>
        </module>
        <!-- 检查字符串字面值是否与==或!=一起使用。因为==将比较对象引用，而不是字符串的实际值，所以应该使用String.equals() -->
        <module name="StringLiteralEquality">
            <property name="severity" value="error"/>
            <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="error"/>
        </module>
        <!-- 检查接口和annotation中是否有多余修饰符，如接口方法不必使用public -->
        <module name="RedundantModifier">
            <property name="severity" value="error"/>
            <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="error"/>
        </module>
        <!-- 检查指定的类型是否声明为要抛出。声明一个方法会引发java.lang.Error或java.lang.RuntimeException几乎是不可接受的 -->
        <module name="IllegalThrows">
            <property name="severity" value="error"/>
        </module>
        <!--检查长匿名内部类长度，最大30  -->
        <module name="AnonInnerLength">
            <property name="severity" value="error"/>
            <property name="max" value="30"/>
        </module>
        <!-- 检查实例变量名称是否符合指定的模式，默认"^[a-z][a-zA-Z0-9]*$" -->
        <module name="MemberName">
            <property name="severity" value="error"/>
        </module>
        <!-- 检查常量名称是否符合指定的模式，默认"^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$" -->
        <module name="ConstantName">
            <property name="severity" value="error"/>
        </module>
        <!-- 包名的检查（只允许小写字母），默认^[a-z]+(\.[a-zA-Z_][a-zA-Z_0-9_]*)*$ -->
        <module name="PackageName">
            <property name="severity" value="error"/>
        </module>
        <!-- 检查default是否在switch语句中的所有case之后。 -->
        <module name="DefaultComesLast">
            <property name="severity" value="error"/>
            <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="error"/>
        </module>
        <!-- 检查某一个类中equals() 和 hashCode() 方法是否同时被重写，即一个被重写，另外一个也必须被重写。通常将二者看成一个override 操作。 -->
        <module name="EqualsHashCode">
            <property name="severity" value="error"/>
            <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="error"/>
        </module>
        <!--不许使用未被简化的布尔返回值

            if (valid()){
                return false;
            }else{
                return true;
            }  -->
        <module name="SimplifyBooleanReturn">
            <property name="severity" value="error"/>
            <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="error"/>
        </module>
        <!-- 检查局部变量或参数是否不影响同一类中定义的字段。 -->
        <module name="HiddenField">
            <property name="severity" value="error"/>
            <property name="tokens" value="VARIABLE_DEF"/>
            <property name="ignoreConstructorParameter" value="true"/>
            <property name="ignoreSetter" value="true"/>
        </module>
        <!-- 检查局部final变量名称是否符合指定的模式。try语句中的catch参数和资源被认为是局部的、最终的变量，默认"^[a-z][a-zA-Z0-9]*$" -->
        <module name="LocalFinalVariableName">
            <property name="severity" value="error"/>
        </module>
        <!-- 函数的分支复杂度，不超过10 -->
        <module name="CyclomaticComplexity">
            <property name="severity" value="error"/>
            <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="error"/>
        </module>
        <!-- 检查类成员的可见性。只有static final、不可变或由指定注释成员注释的才可以是public;除非设置了protectedAllowed或packageAllowed属性，否则其他类成员必须是私有的。  -->
        <module name="VisibilityModifier">
            <property name="severity" value="warning"/>
            <property name="packageAllowed" value="true"/>
            <property name="protectedAllowed" value="true"/>
            <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
        </module>
        <!-- 检查局部的非final变量名是否符合指定的模式。catch参数被认为是一个局部变量，默认"^[a-z][a-zA-Z0-9]*$" -->
        <module name="LocalVariableName">
            <property name="severity" value="error"/>
            <property name="format" value="^[a-z][_a-zA-Z0-9]*$"/>
        </module>
        <!-- 检查方法参数名称是否符合指定的模式，默认"^[a-z][a-zA-Z0-9]*$" -->
        <module name="ParameterName">
            <property name="severity" value="error"/>
        </module>
        <!-- 布尔表达式的复杂度，不超过5 -->
        <module name="BooleanExpressionComplexity">
            <property name="severity" value="error"/>
            <property name="max" value="5"/>
        </module>
        <!-- 检查长方法和构造函数，默认最大150行。不统计空行。  -->
        <module name="MethodLength">
            <property name="severity" value="error"/>
            <property name="countEmpty" value="false"/>
        </module>
        <!-- 将嵌套的if-else块限制到指定的深度  -->
        <module name="NestedIfDepth">
            <property name="severity" value="info"/>
            <property name="max" value="5"/>
        </module>
        <!-- 将嵌套的try-catch-finally块限制到指定的深度。默认1 -->
        <module name="NestedTryDepth">
            <property name="severity" value="error"/>
            <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="error"/>
        </module>
        <!-- 方法的参数个数，默认为7。 -->
        <module name="ParameterNumber">
            <property name="severity" value="error"/>
        </module>
        <!-- 查找嵌套块（在代码中自由使用的块）。 -->
        <module name="AvoidNestedBlocks">
            <property name="severity" value="error"/>
        </module>
        <!-- 检查字符串字面值的任何组合是否位于equals()比较的左侧。还检查分配给某些字段的字符串字面量(例如someString。= (anotherString =“文本”)  -->
        <module name="EqualsAvoidNull">
            <property name="severity" value="error"/>
        </module>
        <!-- 检查优先使用工厂方法的非法实例化。 根据项目的不同，对于某些类，最好通过工厂方法创建实例而不是调用构造函数。 -->
        <module name="IllegalInstantiation">
            <property name="severity" value="error"/>
        </module>
        <!-- 检查 for 循环控制变量在 for 块内没有被修改 -->
        <module name="ModifiedControlVariable">
            <property name="severity" value="error"/>
        </module>
        <!-- Class或Interface名检查，默认^[A-Z][a-zA-Z0-9]*$  -->
        <module name="TypeName"/>
        <!-- 检查数组类型定义的样式 -->
        <module name="ArrayTypeStyle"/>
        <!-- 检查long型定义是否有大写的“L” -->
        <module name="UpperEll"/>
        <!-- 检查所有的interface和class allowUnknownTags：控制在无法识别 Javadoc 标记时是否忽略违规。 -->
        <module name="JavadocType">
            <property name="allowUnknownTags" value="true"/>
        </module>
        <!-- 多余的导入语句 -->
        <module name="RedundantImport"/>
        <!--  检查空块。此检查不验证顺序块。 -->
        <module name="EmptyBlock">
            <property name="severity" value="error"/>
            <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="error"/>
        </module>
        <!-- 检查每个变量声明是否在其自己的语句中并在其自己的行上。 -->
        <module name="MultipleVariableDeclarations"/>
        <!-- 限制方法、构造函数和 lambda 表达式中返回语句的数量。 -->
        <module name="ReturnCount">
            <property name="max" value="4"/>
        </module>
        <!-- 验证 Javadoc 注释以帮助确保它们的格式正确。 -->
        <module name="JavadocStyle">
            <property name="severity" value="ignore"/>
            <property name="checkFirstSentence" value="false"/>
            <metadata name="net.sf.eclipsecs.core.lastEnabledSeverity" value="inherit"/>
        </module>
    </module>
    <!-- 文件长度不超过1500行 -->
    <module name="FileLength">
        <property name="severity" value="error"/>
    </module>
</module>
```

## CheckStyle 升级

maven 配置。

```xml
<dependencies>
    <dependency>
        <groupId>com.puppycrawl.tools</groupId>
        <artifactId>checkstyle</artifactId>
        <version>8.45.1</version>
    </dependency>
</dependencies>
<build>
    <!-- 插件依赖管理 -->
    <pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-checkstyle-plugin</artifactId>
                <version>3.1.2</version>
                <dependencies>
                    <dependency>
                        <groupId>com.puppycrawl.tools</groupId>
                        <artifactId>checkstyle</artifactId>
                        <version>8.45</version>
                    </dependency>
                </dependencies>
                <configuration>
                    <configLocation>checkstyle.xml</configLocation>
                    <encoding>UTF-8</encoding>
                    <consoleOutput>true</consoleOutput>
                    <failsOnError>true</failsOnError>
                    <!-- 警告信息也直接失败 -->
                    <violationSeverity>error</violationSeverity>
                    <linkXRef>false</linkXRef>
                    <outputFile>target/reports/checkstyle/checkstyle-result.xml</outputFile>
                    <outputDirectory>target/reports/checkstyle</outputDirectory>
                    <outputFileFormat>xml</outputFileFormat>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <!-- verify 阶段 -->
                            <goal>check</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </pluginManagement>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-checkstyle-plugin</artifactId>
        </plugin>
        <plugin>
            <groupId>org.jacoco</groupId>
            <artifactId>jacoco-maven-plugin</artifactId>
            <version>0.8.7</version>
            <executions>
                <execution>
                    <goals>
                        <goal>prepare-agent</goal>
                    </goals>
                </execution>
                <!-- attached to Maven test phase -->
                <execution>
                    <id>report</id>
                    <phase>test</phase>
                    <goals>
                        <goal>report</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
```

## CheckStyle 升级

```xml
<?xml version="1.0"?>

<!DOCTYPE module PUBLIC "-//Checkstyle//DTD Checkstyle Configuration 1.3//EN" "https://checkstyle.org/dtds/configuration_1_3.dtd">

<!--
This is a checkstyle configuration file. For descriptions of
what the following rules do, please see the checkstyle configuration
page at http://checkstyle.sourceforge.net/config.html.
This file is based on the checkstyle file of Apache Beam.
-->

<module name="Checker">
    <module name="FileLength">
        <property name="max" value="1500" />
    </module>
    <!--   单行代码长度 -->
    <module name="LineLength">
        <!-- Checks if a line is too long. -->
        <property name="max" value="150" />
        <property name="ignorePattern" value="^(package .*;\s*)|(import .*;\s*)|( *\* .*https?://.*)$" />
    </module>

    <!-- All Java AST specific tests live under TreeWalker module. -->
    <module name="TreeWalker">
        <!-- 参数分配通常被认为是糟糕的编程实践。强制开发人员将参数声明为 final 通常是繁重的。检查确保永远不会分配参数将两全其美。 -->
        <module name="ParameterAssignment" />

        <!-- 不许使用未被简化的条件表达式 -->
        <module name="SimplifyBooleanExpression" />

        <!-- 检查静态的非final变量名是否符合指定的模式，默认"^[a-z][a-zA-Z0-9]*$" -->
        <module name="StaticVariableName" />

        <!-- 检查方法名称是否符合指定的模式，默认"^[a-z][a-zA-Z0-9]*$" -->
        <module name="MethodName" />
        <!-- 不许内部赋值 String s = Integer.toString(i = 2) -->
        <module name="InnerAssignment" />

        <!-- 检查未使用的导入包 -->
        <module name="UnusedImports">
            <property name="processJavadoc" value="true" />
            <message key="import.unused" value="Unused import: {0}." />
        </module>
        <!-- 检查字符串字面值是否与==或!=一起使用。因为==将比较对象引用，而不是字符串的实际值，所以应该使用String.equals() -->
        <module name="StringLiteralEquality" />

        <!-- 检查接口和annotation中是否有多余修饰符，如接口方法不必使用public -->
        <module name="RedundantModifier">
            <property name="tokens" value="VARIABLE_DEF, ANNOTATION_FIELD_DEF, INTERFACE_DEF, CLASS_DEF, ENUM_DEF" />
        </module>
        <!-- 检查指定的类型是否声明为要抛出。声明一个方法会引发java.lang.Error或java.lang.RuntimeException几乎是不可接受的 -->
        <module name="IllegalThrows" />

        <!--检查长匿名内部类长度，最大30  -->
        <module name="AnonInnerLength">
            <property name="max" value="30" />
        </module>
        <!-- 检查实例变量名称是否符合指定的模式，默认"^[a-z][a-zA-Z0-9]*$" -->
        <module name="MemberName" />

        <!-- 检查常量名称是否符合指定的模式，默认"^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$" -->
        <module name="ConstantName" />
        <!-- 包名的检查（只允许小写字母），默认^[a-z]+(\.[a-zA-Z_][a-zA-Z_0-9_]*)*$ -->
        <module name="PackageName">
            <property name="format" value="^[a-z]+(\.[a-z][a-z0-9]{1,})*$" />
        </module>
        <!-- 检查default是否在switch语句中的所有case之后。 -->
        <module name="DefaultComesLast" />

        <!-- 检查某一个类中equals() 和 hashCode() 方法是否同时被重写，即一个被重写，另外一个也必须被重写。通常将二者看成一个override 操作。 -->
        <module name="EqualsHashCode" />

        <!--不许使用未被简化的布尔返回值

            if (valid()){
                return false;
            }else{
                return true;
            }  -->
        <module name="SimplifyBooleanReturn" />

        <!-- 检查局部变量或参数是否不影响同一类中定义的字段。 -->
        <module name="HiddenField">
            <property name="tokens" value="VARIABLE_DEF" />
            <property name="ignoreConstructorParameter" value="true" />
            <property name="ignoreSetter" value="true" />
        </module>
        <!-- 检查局部final变量名称是否符合指定的模式。try语句中的catch参数和资源被认为是局部的、最终的变量，默认"^[a-z][a-zA-Z0-9]*$" -->
        <module name="LocalFinalVariableName" />
        <!-- 函数的分支复杂度，不超过10 -->
        <module name="CyclomaticComplexity" />
        <!-- 检查类成员的可见性。只有static final、不可变或由指定注释成员注释的才可以是public;除非设置了protectedAllowed或packageAllowed属性，否则其他类成员必须是私有的。  -->
        <module name="VisibilityModifier">
            <property name="severity" value="warning" />
            <property name="packageAllowed" value="true" />
            <property name="protectedAllowed" value="true" />
        </module>
        <!-- 检查局部的非final变量名是否符合指定的模式。catch参数被认为是一个局部变量，默认"^[a-z][a-zA-Z0-9]*$" -->
        <module name="LocalVariableName">
            <property name="format" value="^[a-z][_a-zA-Z0-9]*$" />
        </module>
        <!-- 检查方法参数名称是否符合指定的模式，默认"^[a-z][a-zA-Z0-9]*$" -->
        <module name="ParameterName" />
        <!-- 布尔表达式的复杂度，不超过5 -->
        <module name="BooleanExpressionComplexity">
            <property name="max" value="5" />
        </module>
        <!-- 检查长方法和构造函数，默认最大150行。不统计空行。  -->
        <module name="MethodLength">
            <property name="countEmpty" value="false" />
        </module>
        <!-- 将嵌套的if-else块限制到指定的深度  -->
        <module name="NestedIfDepth">
            <property name="severity" value="info" />
            <property name="max" value="5" />
        </module>
        <!-- 将嵌套的try-catch-finally块限制到指定的深度。默认1 -->
        <module name="NestedTryDepth" />

        <!-- 方法的参数个数，默认为7。 -->
        <module name="ParameterNumber" />
        <!-- 查找嵌套块（在代码中自由使用的块）。 -->
        <module name="AvoidNestedBlocks" />
        <!-- 检查字符串字面值的任何组合是否位于equals()比较的左侧。还检查分配给某些字段的字符串字面量(例如someString。= (anotherString =“文本”)  -->
        <module name="EqualsAvoidNull" />

        <!-- 检查优先使用工厂方法的非法实例化。 根据项目的不同，对于某些类，最好通过工厂方法创建实例而不是调用构造函数。 -->
        <module name="IllegalInstantiation" />
        <!-- 检查 for 循环控制变量在 for 块内没有被修改 -->
        <module name="ModifiedControlVariable" />
        <!-- Class或Interface名检查，默认^[A-Z][a-zA-Z0-9]*$  -->
        <module name="TypeName" />
        <!-- 检查数组类型定义的样式 -->
        <module name="ArrayTypeStyle" />
        <!-- 检查long型定义是否有大写的“L” -->
        <module name="UpperEll" />
        <!-- 检查所有的interface和class allowUnknownTags：控制在无法识别 Javadoc 标记时是否忽略违规。 -->
        <module name="JavadocType">
            <property name="allowUnknownTags" value="true" />
        </module>
        <!-- 多余的导入语句 -->
        <module name="RedundantImport">
            <!-- Checks for redundant import statements. -->
            <message key="import.redundancy" value="Redundant import {0}." />
        </module>
        <!--  检查空块。此检查不验证顺序块。 -->
        <module name="EmptyBlock" />

        <!-- 检查每个变量声明是否在其自己的语句中并在其自己的行上。 -->
        <module name="MultipleVariableDeclarations" />
        <!-- 限制方法、构造函数和 lambda 表达式中返回语句的数量。 -->
        <module name="ReturnCount">
            <property name="max" value="4" />
        </module>
        <!-- 验证 Javadoc 注释以帮助确保它们的格式正确。 -->
        <module name="JavadocStyle">
            <property name="severity" value="ignore" />
            <property name="checkFirstSentence" value="false" />
        </module>


        <!--
        FLINK CUSTOM CHECKS
        -->
        <!-- 这个是换行使用 tab 缩进       -->
        <module name="RegexpSinglelineJava">
            <property name="format" value="^\t* +\t*\S" />
            <property name="message" value="Line has leading space characters; indentation should be performed with tabs only." />
            <property name="ignoreComments" value="true" />
            <!--  每个文件最多出现100个.相当于先屏蔽掉  -->
            <property name="maximum" value="100" />
        </module>
        <!-- 检查是否有多个分号出现 (e.g. ";;"). -->
        <module name="RegexpSinglelineJava">
            <property name="format" value=";{2,}" />
            <property name="message" value="Use one semicolon" />
            <property name="ignoreComments" value="true" />
        </module>
        <!-- Prohibit T.getT() methods for standard boxed types -->
        <module name="Regexp">
            <property name="format" value="Boolean\.getBoolean" />
            <property name="illegalPattern" value="true" />
            <property name="message" value="Use System.getProperties() to get system properties." />
        </module>

        <module name="Regexp">
            <property name="format" value="Integer\.getInteger" />
            <property name="illegalPattern" value="true" />
            <property name="message" value="Use System.getProperties() to get system properties." />
        </module>

        <module name="Regexp">
            <property name="format" value="Long\.getLong" />
            <property name="illegalPattern" value="true" />
            <property name="message" value="Use System.getProperties() to get system properties." />
        </module>

        <!--
        IllegalImport cannot blacklist classes so we have to fall back to Regexp.
        下面这些包名，或者类名，已经被加入黑名单，禁止使用。
        -->

        <!-- forbid use of commons lang validate -->
        <module name="Regexp">
            <property name="format" value="org\.apache\.commons\.lang3\.Validate" />
            <property name="illegalPattern" value="true" />
            <property name="message" value="Use Guava Checks instead of Commons Validate. Please refer to the coding guidelines." />
        </module>
        <module name="Regexp">
            <property name="format" value="org\.apache\.commons\.lang\." />
            <property name="illegalPattern" value="true" />
            <property name="message" value="Use commons-lang3 instead of commons-lang." />
        </module>
        <module name="Regexp">
            <property name="format" value="org\.codehaus\.jettison" />
            <property name="illegalPattern" value="true" />
            <property name="message" value="Use com.fasterxml.jackson instead of jettison." />
        </module>

        <module name="TodoComment" />
        <module name="AvoidStarImport">
            <property name="severity" value="warning" />
        </module>

        <!--   禁止使用的开发包,被加入黑名单的包     -->
        <module name="IllegalImport">
            <property name="illegalPkgs" value="autovalue.shaded, avro.shaded, com.google.api.client.repackaged, com.google.appengine.repackaged, org.codehaus.jackson, io.netty, org.objectweb.asm, com.google.common" />
        </module>


        <!--
        JAVADOC CHECKS
        -->
        <!-- Checks for Javadoc comments.                     -->
        <!-- See http://checkstyle.sf.net/config_javadoc.html -->
        <module name="JavadocMethod">
            <property name="allowMissingParamTags" value="true" />
            <property name="allowMissingReturnTag" value="true" />
        </module>

        <module name="JavadocType">
            <property name="scope" value="protected" />
            <property name="allowMissingParamTags" value="true" />
        </module>


        <!--
        NAMING CHECKS
        -->

        <!-- Validates static, final fields against the expression "^[A-Z][a-zA-Z0-9]*$". -->
        <module name="TypeNameCheck">
            <metadata name="altname" value="TypeName" />
        </module>
        <!--    常量名称检测    -->
        <module name="ConstantNameCheck">
            <!-- Validates non-private, static, final fields against the supplied
            public/package final fields "^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$". -->
            <metadata name="altname" value="ConstantName" />
            <property name="applyToPublic" value="true" />
            <property name="applyToProtected" value="true" />
            <property name="applyToPackage" value="true" />
            <property name="applyToPrivate" value="false" />
            <property name="format" value="^([A-Z][A-Z0-9]*(_[A-Z0-9]+)*|FLAG_.*)$" />
            <message key="name.invalidPattern" value="Variable ''{0}'' should be in ALL_CAPS (if it is a constant) or be private (otherwise)." />
        </module>

        <module name="StaticVariableNameCheck">
            <!-- Validates static, non-final fields against the supplied
            expression "^[a-z][a-zA-Z0-9]*_?$". -->
            <metadata name="altname" value="StaticVariableName" />
            <property name="applyToPublic" value="true" />
            <property name="applyToProtected" value="true" />
            <property name="applyToPackage" value="true" />
            <property name="applyToPrivate" value="true" />
            <property name="format" value="^[a-z][a-zA-Z0-9]*_?$" />
        </module>

        <module name="MemberNameCheck">
            <!-- Validates non-static members against the supplied expression. -->
            <metadata name="altname" value="MemberName" />
            <property name="applyToPublic" value="true" />
            <property name="applyToProtected" value="true" />
            <property name="applyToPackage" value="true" />
            <property name="applyToPrivate" value="true" />
            <property name="format" value="^[a-z][a-zA-Z0-9]*$" />
        </module>

        <module name="MethodNameCheck">
            <!-- Validates identifiers for method names. -->
            <metadata name="altname" value="MethodName" />
            <property name="format" value="^[a-z][a-zA-Z0-9]*(_[a-zA-Z0-9]+)*$" />
        </module>


        <!--
        LENGTH and CODING CHECKS
        -->

        <!--  检查代码块的左花括号 ('{') 的位置-->
        <module name="LeftCurly" />

        <!--   检查代码块的右花括号 ('}') 的位置     -->
        <module name="RightCurly">
            <!-- Checks right curlies on CATCH, ELSE, and TRY blocks are on
            the same line. e.g., the following example is fine:
            <pre>
            if {
            ...
            } else
            </pre>
            -->
            <!-- This next example is not fine:
            <pre>
            if {
            ...
            }
            else
            </pre>
            -->
            <property name="option" value="same" />
        </module>

        <!-- 检查代码块周围的大括号。 -->
        <module name="NeedBraces">
            <property name="tokens" value="LITERAL_IF, LITERAL_ELSE, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO" />
        </module>

        <!--      检查 switch 语句中的失败。查找 case 包含 Java 代码但缺少 break、return、throw 或 continue 语句的位置-->

        <module name="FallThrough">
            <property name="reliefPattern" value="fall through|Fall through|fallthru|Fallthru|falls through|Falls through|fallthrough|Fallthrough|No break|NO break|no break|continue on" />
        </module>

        <!-- Detects empty statements (standalone ";" semicolon). -->
        <module name="EmptyStatement" />

        <!--
        MODIFIERS CHECKS
        -->

        <!--   检查修饰符的顺序，是否符合 java 标准
        public
        protected
        private
        abstract
        default
        static
        sealed
        non-sealed
        final
        transient
        volatile
        synchronized
        native
        strictfp
        -->
        <module name="ModifierOrder" />


        <!--
        WHITESPACE CHECKS
        -->
        <!-- Checks for empty line separator between tokens. The only
                     excluded token is VARIABLE_DEF, allowing class fields to
                     be declared on consecutive lines.
                -->
        <module name="EmptyLineSeparator">

            <property name="allowMultipleEmptyLines" value="false" />
            <property name="allowMultipleEmptyLinesInsideClassMembers" value="false" />
            <property name="tokens" value="PACKAGE_DEF, IMPORT, CLASS_DEF,
        INTERFACE_DEF, ENUM_DEF, STATIC_INIT, INSTANCE_INIT, METHOD_DEF,
        CTOR_DEF" />
        </module>

        <!-- Checks that various tokens are surrounded by whitespace.
            This includes most binary operators and keywords followed
            by regular or curly braces.
       -->
        <module name="WhitespaceAround">
            <property name="tokens" value="ASSIGN, BAND, BAND_ASSIGN, BOR,
        BOR_ASSIGN, BSR, BSR_ASSIGN, BXOR, BXOR_ASSIGN, COLON, DIV, DIV_ASSIGN,
        EQUAL, GE, GT, LAND, LE, LITERAL_CATCH, LITERAL_DO, LITERAL_ELSE,
        LITERAL_FINALLY, LITERAL_FOR, LITERAL_IF, LITERAL_RETURN,
        LITERAL_SYNCHRONIZED, LITERAL_TRY, LITERAL_WHILE, LOR, LT, MINUS,
        MINUS_ASSIGN, MOD, MOD_ASSIGN, NOT_EQUAL, PLUS, PLUS_ASSIGN, QUESTION,
        SL, SL_ASSIGN, SR_ASSIGN, STAR, STAR_ASSIGN" />

        </module>

        <!-- Checks that commas, semicolons and typecasts are followed by
               whitespace.
          -->
        <module name="WhitespaceAfter">
            <property name="tokens" value="COMMA, SEMI, TYPECAST" />
        </module>
        <!-- Checks that there is no whitespace after various unary operators.
              Linebreaks are allowed.
         -->
        <module name="NoWhitespaceAfter">
            <property name="tokens" value="BNOT, DEC, DOT, INC, LNOT, UNARY_MINUS,UNARY_PLUS" />
            <property name="allowLineBreaks" value="true" />
        </module>

        <!-- Checks that there is no whitespace before various unary operators.
               Linebreaks are allowed.
          -->
        <module name="NoWhitespaceBefore">
            <property name="tokens" value="SEMI, DOT, POST_DEC, POST_INC" />
            <property name="allowLineBreaks" value="true" />
        </module>

        <!-- Checks that operators like + and ? appear at newlines rather than
        at the end of the previous line.
        -->
        <module name="OperatorWrap">
            <property name="option" value="NL" />
            <property name="tokens" value="BAND, BOR, BSR, BXOR, DIV, EQUAL,
            GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR, LT, MINUS, MOD,
            NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR " />
        </module>

        <!-- Checks that assignment operators are at the end of the line. -->
        <module name="OperatorWrap">
            <property name="option" value="eol" />
            <property name="tokens" value="ASSIGN" />
        </module>

        <!--        检查有关括号填充的策略-->
        <!-- Checks that there is no whitespace before close parens or after
          open parens. -->
        <module name="ParenPad" />
    </module>
</module>
```


# Golang

   本章主要记录在学习和使用Golang过程中遇到的一些问题，以及生产中解决的一些问题。


# 源码阅读


# Goroutines

推荐一本书 [《Concurrency in go 》](https://www.kancloud.cn/mutouzhang/go/596804)

* [Goroutine](/golang/source/goroutine#goroutine)
  * [提出问题](https://www.selinux.tech/golang/source/pages/-Lcj5NfWlR6qzY65BaNV#提出问题)
  * [进程与线程](https://www.selinux.tech/golang/source/pages/-Lcj5NfWlR6qzY65BaNV#进程与线程)
    * [产生的背景](https://www.selinux.tech/golang/source/pages/-Lcj5NfWlR6qzY65BaNV#产生的背景)
    * [相关概念](https://www.selinux.tech/golang/source/pages/-Lcj5NfWlR6qzY65BaNV#相关概念)
      * [Process](/golang/source/goroutine#process)
      * [thread](/golang/source/goroutine#thread)
      * [other](/golang/source/goroutine#other)
  * [并发（concurrency）和并行（parallelism）](https://www.selinux.tech/golang/source/pages/-Lcj5NfWlR6qzY65BaNV#并发concurrency和并行parallelism)
  * [进程间通信（IPC，Inter-Process Communication）](https://www.selinux.tech/golang/source/pages/-Lcj5NfWlR6qzY65BaNV#进程间通信ipcinter-process-communication)
  * [线程同步](https://www.selinux.tech/golang/source/pages/-Lcj5NfWlR6qzY65BaNV#线程同步)
    * [同步的方法(以java为例)](https://www.selinux.tech/golang/source/pages/-Lcj5NfWlR6qzY65BaNV#同步的方法以java为例)
  * [Go的并发哲学](https://www.selinux.tech/golang/source/pages/-Lcj5NfWlR6qzY65BaNV#go的并发哲学)
  * [协程](https://www.selinux.tech/golang/source/pages/-Lcj5NfWlR6qzY65BaNV#协程)
    * [什么是协程](https://www.selinux.tech/golang/source/pages/-Lcj5NfWlR6qzY65BaNV#什么是协程)
    * [协程的调度(MPG)](https://www.selinux.tech/golang/source/pages/-Lcj5NfWlR6qzY65BaNV#协程的调度mpg)
    * [协程(Goroutine scheduler)的源码实现](https://www.selinux.tech/golang/source/pages/-Lcj5NfWlR6qzY65BaNV#协程goroutine-scheduler的源码实现)
  * [一些参考](https://www.selinux.tech/golang/source/pages/-Lcj5NfWlR6qzY65BaNV#一些参考)

## 提出问题

* go 是如何天然支持高并发的？
* goroutine的调度机制？
* goroutine的底层实现？
* channel的底层实现？
* channel如何做到了goroutine-safe?

## 进程与线程

在最开始，我们先来老生长谈一下什么是进程、线程。 或许每个人都知道进程和线程，但是有几个人能够快速准确的说出进程是什么呢？

维基百科中关于 [进程的定义](https://zh.wikipedia.org/wiki/行程)

### 产生的背景

* 由用户输入指令的低效 -->
* 批处理操作系统的串行 -->
* CPU时间片轮转实现操作系统的并发 -->
* 线程实现了进程内部的并发

可以参考 [深入浅出java多线程](https://redspider.gitbook.io/concurrent/di-yi-pian-ji-chu-pian/1)

### 相关概念

#### Process

* 面向进程的操作系统（早期的UNIX，Linux 2.4）是程序执行的基本单位
* 面向线程的操作系统（Linux 2.6之后）进程不是基本单位而是线程的容器
* 进程五种状态（new、running、waiting、ready、terminated）

#### thread

* 操作系统运算调度的最小单位,在进程之中，是进程的实际运行单位.
* 进程中可以有多个执行不同任务的并发线程
* 在Unix System V及SunOS中也被称为轻量进程（lightweight processes），但轻量进程更多指内核线程（kernel thread），而把用户线程（user thread）称为线程。

#### other

* 进程优先级
* 线程优先级

## 并发（concurrency）和并行（parallelism）

“并发”指的是程序的结构，“并行”指的是程序运行时的状态. 并行指物理上同时执行，并发指能够让多个任务在逻辑上交织执行的程序设计.

Concurrency is about dealing with lots of things at once.

Parallelism is about doing lots of things at once.

Not the same, but related.

Concurrency is about structure, parallelism is about execution.

Concurrency provides a way to structure a solution to solve a problem that may (but not necessarily) be parallelizable.

go 官方有一篇bolg [Concurrency is not parallelism](https://blog.golang.org/concurrency-is-not-parallelism)，接下来我们就以官方的slide来认识一下并发和并行。[点击这里](https://talks.golang.org/2012/waza.slide#1)

## 进程间通信（IPC，Inter-Process Communication）

进程是计算机系统分配资源的最小单位（严格说来是线程）。每个进程都有自己的一部分独立的系统资源，彼此是隔离的。为了能使不同的进程互相访问资源并进行协调工作，就有了进程间通信.

主要的IPC方法

* 文件(文件锁 log-->file-->filebeat)
* [信号](https://zh.wikipedia.org/wiki/Unix信号) (SIGILL、SIGKILL、ctrl+c==SIGINT ...)
* [socket](https://en.wikipedia.org/wiki/Berkeley_sockets) (tcp 三次握手)
* [message queue](https://en.wikipedia.org/wiki/Message_queue)
* 管道pipe (ps -aux | grep nginx)
* [命名管道FIFO](https://en.wikipedia.org/wiki/Named_pipe) (mkfifo my\_pipe ; gzip -9 -c < my\_pipe > out.gz &  ;cat file > my\_pipe) &#x20;
* [信号量](https://zh.wikipedia.org/wiki/信号量) (信号量与信号不是一个问题，P(wait) 申请,V(signal)释放)
* [共享内存](https://en.wikipedia.org/wiki/Shared_memory)
* [消息传递](https://en.wikipedia.org/wiki/Message_passing)（filebeat --> logstash -->es）
* [内存映射文件](https://en.wikipedia.org/wiki/Memory-mapped_file)

## 线程同步

线程同步被定义为一种机制，可确保两个或多个并发进程或线程不会同时执行称为临界区的某个特定程序段.

* [死锁](https://en.wikipedia.org/wiki/Deadlock)，当许多进程正在等待某个其他进程持有的共享资源（临界区）时发生。在这种情况下，进程只是等待并且不再执行;
* \[饥饿]\(<https://en.wikipedia.org/wiki/Starvation_(computer_science))，当进程等待进入临界区时发生，但其他进程独占关键部分，第一个进程被迫无限期等待>;
* [优先级倒置](https://en.wikipedia.org/wiki/Priority_inversion)，在高优先级进程处于临界区时发生，并由中优先级进程中断。这种违反优先权规则的行为可能会在某些情况下发生，并可能导致实时系统的严重后果;
* [忙等待](https://en.wikipedia.org/wiki/Busy_waiting)，当进程经常轮询以确定它是否可以访问关键部分时发生。这种频繁的轮询会拖延其他进程的处理时间。

### 同步的方法(以java为例)

* 同步方法

```java
public synchronized void save(){}
```

* 同步代码块

```java
synchronized(object){}
```

* 使用特殊域变量(volatile)实现线程同步

```java
可以参考前面线程安全的单例模式
```

* 使用重入锁实现线程同步

ReenreantLock类的常用方法有： ReentrantLock() : 创建一个ReentrantLock实例 lock() : 获得锁 unlock() : 释放锁

这种方式类似于golang中的lock。

```java
class Test {

            private int count = 100;
            //需要声明这个锁
            private Lock lock = new ReentrantLock();
            public int getAccount() {
                return account;
            }
            //这里不再需要synchronized
            public void save(int num) {
                lock.lock();
                try{
                    count += num;
                }finally{
                    lock.unlock();
                }
            }
        ｝
```

* ...等等其他的方式

## Go的并发哲学

Go鼓励使用channel在goroutine之间传递数据，而不是显式地使用锁来调解对共享数据的访问.[Share Memory By Communicating](https://blog.golang.org/share-memory-by-communicating)

[《Cuncurrency in Go》Go的并发哲学](https://www.kancloud.cn/mutouzhang/go/596822)

## 协程

下面来到了今天的主角，Goroutine。

### 什么是协程

Goroutine是Go中最基本的组织单位之一，最直观来说，gotoutine就是一个并发的函数（记住：不一定是并行）和其他代码一起运行.

```go
func main() {
    go sayHello()
    // continue doing other things
}

func sayHello() {
    fmt.Println("hello")
}
```

或者使用匿名函数来启动go协程

```go
go func(str string) {
fmt.Println(str)
}("hello")
```

Goroutines对Go来说是独一无二的。它们不是操作系统线程，它们不完全是绿色的线程(例如JVM管理的线程)，它们是更高级别的抽象，被称为协程(coroutines)。协程是非抢占的并发子程序，也就是说，它们不能被中断。

下面是从[goroutine背后的知识](http://www.sizeofvoid.net/goroutine-under-the-hood/)摘录的几点内容。

* goroutine是Go语言运行库的功能，不是操作系统提供的功能，goroutine不是用线程实现的。
* goroutine就是一段代码，一个函数入口，以及在堆上为其分配的一个堆栈。所以它非常廉价，我们可以很轻松的创建上万个goroutine，但它们并不是被操作系统所调度执行,go runtime 有自己的scheduler。
* 除了被系统调用阻塞的线程外，Go运行库最多会启动$GOMAXPROCS个线程来运行goroutine，现在go应用程序已经默认使用$GOMAXPROCS
* goroutine是协作式调度的，如果goroutine会执行很长时间，而且不是通过等待读取或写入channel的数据来同步的话，就需要主动调用Gosched()来让出CPU
* 和所有其他并发框架里的协程一样，goroutine里所谓“无锁”的优点只在单线程下有效，如果$GOMAXPROCS > 1并且协程间需要通信，Go运行库会负责加锁保护数据
* **Web等服务端程序要处理的请求从本质上来讲是并行处理的问题，每个请求基本独立，互不依赖，几乎没有数据交互，这不是一个并发编程的模型**，而并发编程框架只是解决了其语义表述的复杂性，并不是从根本上提高处理的效率，也许是并发连接和并发编程的英文都是concurrent吧，很容易产生“并发编程框架和coroutine可以高效处理大量并发连接”的误解。

### 协程的调度(MPG)

如果要理解协程的调度，或者看懂 Goroutine scheduler 代码的话，就不得不了解一下MPG。

我们引出三个定义

```go
G - goroutine.
M - worker thread, or machine.
P - processor, a resource that is required to execute Go code.
    M must have an associated P to execute Go code, however it can be
    blocked or in a syscall w/o an associated P.
```

> 下面的内容引用自[协程的实现原理](https://www.cnblogs.com/zkweb/p/7815600.html)

**G (goroutine)** G是goroutine的头文字, goroutine可以解释为受管理的轻量线程, goroutine使用go关键词创建.

举例来说, func main() { go other() }, 这段代码创建了两个goroutine, 一个是main, 另一个是other, 注意main本身也是一个goroutine.

goroutine的新建, 休眠, 恢复, 停止都受到go运行时的管理. goroutine执行异步操作时会进入休眠状态, 待操作完成后再恢复, 无需占用系统线程, goroutine新建或恢复时会添加到运行队列, 等待M取出并运行.

**M (machine)** M是machine的头文字, 在当前版本的golang中等同于系统线程. M可以运行两种代码:

go代码, 即goroutine, M运行go代码需要一个P 原生代码, 例如阻塞的syscall, M运行原生代码不需要P M会从运行队列中取出G, 然后运行G, 如果G运行完毕或者进入休眠状态, 则从运行队列中取出下一个G运行, 周而复始. 有时候G需要调用一些无法避免阻塞的原生代码, 这时M会释放持有的P并进入阻塞状态, 其他M会取得这个P并继续运行队列中的G. go需要保证有足够的M可以运行G, 不让CPU闲着, 也需要保证M的数量不能过多.

**P (process)** P是process的头文字, 代表M运行G所需要的资源. 一些讲解协程的文章把P理解为cpu核心, 其实这是错误的. 虽然P的数量默认等于cpu核心数, 但可以通过环境变量GOMAXPROC修改, 在实际运行时P跟cpu核心并无任何关联.

P也可以理解为控制go代码的并行度的机制, 如果P的数量等于1, 代表当前最多只能有一个线程(M)执行go代码, 如果P的数量等于2, 代表当前最多只能有两个线程(M)执行go代码. 执行原生代码的线程数量不受P控制.

因为同一时间只有一个线程(M)可以拥有P, P中的数据都是锁自由(lock free)的, 读写这些数据的效率会非常的高.

**关于协程的调度，我们用下面几张简单的图示来进行一下讲解。**\
**用下面三个图形来表示。**\
![MPG](/files/-LckHvmaRBogk-e4wNdO)

**MPG的运行状态I**\
![MPG的运行状态Ⅰ](/files/-LckHvmcz37gp-Z32rcZ)

* 当前程序有三个M，如果三个M在同一个CPU上运行就是并发，不同CPU就是并行。
* M1.M2,M3都在执行G，同时队列中分别有不同的G在等待获取P，来运行。
* 从这个图的感觉上来看，这一点与线程有点类似，但是Goroutine是逻辑态的，因此go很容易就开启上万Goroutine。
* 其他编程语言实现的线程往往是内核态或者用户态，有时还需要互相转化，比较重量级，很容易耗光物理资源。

**MPG的运行状态Ⅱ**\
![MPG的运行状态Ⅱ](/files/-LckHvmeW-xxS4eBfFbW)

* 我们分成两个部分来看，首先看左边的部分。M0正在执行G0，另外有三个协程在等待。
* 如果G0阻塞，比如读取文件或者数据库读写等
* 这时就会创建M1 Worker thread(也有可能从现有的线程池中取出新的线程),并且将等待的三个G放到M1下开始执行,M0下的G0依然执行自己的耗时操作。
* 这样的MPG调度模式,可以既让G0执行，同时也不会让队列的其他协程一直阻塞，仍然可以并发/并行执行。
* 等到G0不阻塞了，M0会被放到空闲的主线程(从已有的线程池中取)继续执行。

MPG的源代码在 [src/runtime/rumtime2.go](https://github.com/golang/go/blob/master/src/runtime/runtime2.go)

> 下面的内容引用自[协程的实现原理](https://www.cnblogs.com/zkweb/p/7815600.html)

G里面比较重要的成员如下

* stack: 当前g使用的栈空间, 有lo和hi两个成员
* stackguard0: 检查栈空间是否足够的值, 低于这个值会扩张栈, 0是go代码使用的
* stackguard1: 检查栈空间是否足够的值, 低于这个值会扩张栈, 1是原生代码使用的
* m: 当前g对应的m
* sched: g的调度数据, 当g中断时会保存当前的pc和rsp等值到这里, 恢复运行时会使用这里的值
* atomicstatus: g的当前状态
* schedlink: 下一个g, 当g在链表结构中会使用
* preempt: g是否被抢占中
* lockedm: g是否要求要回到这个M执行, 有的时候g中断了恢复会要求使用原来的M执行

M里面比较重要的成员如下

* g0: 用于调度的特殊g, 调度和执行系统调用时会切换到这个g
* curg: 当前运行的g
* p: 当前拥有的P
* nextp: 唤醒M时, M会拥有这个P
* park: M休眠时使用的信号量, 唤醒M时会通过它唤醒
* schedlink: 下一个m, 当m在链表结构中会使用
* mcache: 分配内存时使用的本地分配器, 和p.mcache一样(拥有P时会复制过来)
* lockedg: lockedm的对应值

P里面比较重要的成员如下

* status: p的当前状态
* link: 下一个p, 当p在链表结构中会使用
* m: 拥有这个P的M
* mcache: 分配内存时使用的本地分配器
* runqhead: 本地运行队列的出队序号
* runqtail: 本地运行队列的入队序号
* runq: 本地运行队列的数组, 可以保存256个G
* gfree: G的自由列表, 保存变为\_Gdead后可以复用的G实例
* gcBgMarkWorker: 后台GC的worker函数, 如果它存在M会优先执行它
* gcw: GC的本地工作队列, 详细将在下一篇(GC篇)分析

**MPG的状态定义** 下面我们贴一下MPG的状态定义，这将有利于我们在后面分析goroutine源码。

首先是G的状态定义，状态都是常量。

```go
// defined constants
const (
    // G status
    //
    // Beyond indicating the general state of a G, the G status
    // acts like a lock on the goroutine's stack (and hence its
    // ability to execute user code).
    //
    // If you add to this list, add to the list
    // of "okay during garbage collection" status
    // in mgcmark.go too.
    //
    // TODO(austin): The _Gscan bit could be much lighter-weight.
    // For example, we could choose not to run _Gscanrunnable
    // goroutines found in the run queue, rather than CAS-looping
    // until they become _Grunnable. And transitions like
    // _Gscanwaiting -> _Gscanrunnable are actually okay because
    // they don't affect stack ownership.

    // _Gidle means this goroutine was just allocated and has not
    // yet been initialized.
    // 表示G刚刚新建, 仍未初始化
    _Gidle = iota // 0

    // _Grunnable means this goroutine is on a run queue. It is
    // not currently executing user code. The stack is not owned.
    // 表示G在运行队列中, 等待M取出并运行
    _Grunnable // 1

    // _Grunning means this goroutine may execute user code. The
    // stack is owned by this goroutine. It is not on a run queue.
    // It is assigned an M and a P.
    // 表示M正在运行这个G, 这时候M会拥有一个P
    _Grunning // 2

    // _Gsyscall means this goroutine is executing a system call.
    // It is not executing user code. The stack is owned by this
    // goroutine. It is not on a run queue. It is assigned an M.
    // 表示M正在运行这个G发起的系统调用, 这时候M并不拥有P
    _Gsyscall // 3

    // _Gwaiting means this goroutine is blocked in the runtime.
    // It is not executing user code. It is not on a run queue,
    // but should be recorded somewhere (e.g., a channel wait
    // queue) so it can be ready()d when necessary. The stack is
    // not owned *except* that a channel operation may read or
    // write parts of the stack under the appropriate channel
    // lock. Otherwise, it is not safe to access the stack after a
    // goroutine enters _Gwaiting (e.g., it may get moved).
    // 表示G在等待某些条件完成, 这时候G不在运行也不在运行队列中(可能在channel的等待队列中)
    _Gwaiting // 4

    // _Gmoribund_unused is currently unused, but hardcoded in gdb
    // scripts.
    _Gmoribund_unused // 5

    // _Gdead means this goroutine is currently unused. It may be
    // just exited, on a free list, or just being initialized. It
    // is not executing user code. It may or may not have a stack
    // allocated. The G and its stack (if any) are owned by the M
    // that is exiting the G or that obtained the G from the free
    // list.
    // 表示G未被使用, 可能已执行完毕(并在freelist中等待下次复用)
    _Gdead // 6

    // _Genqueue_unused is currently unused.
    _Genqueue_unused // 7

    // _Gcopystack means this goroutine's stack is being moved. It
    // is not executing user code and is not on a run queue. The
    // stack is owned by the goroutine that put it in _Gcopystack.
    //  表示G正在获取一个新的栈空间并把原来的内容复制过去(用于防止GC扫描)
    _Gcopystack // 8

    // _Gscan combined with one of the above states other than
    // _Grunning indicates that GC is scanning the stack. The
    // goroutine is not executing user code and the stack is owned
    // by the goroutine that set the _Gscan bit.
    //
    // _Gscanrunning is different: it is used to briefly block
    // state transitions while GC signals the G to scan its own
    // stack. This is otherwise like _Grunning.
    //
    // atomicstatus&~Gscan gives the state the goroutine will
    // return to when the scan completes.
    _Gscan         = 0x1000
    _Gscanrunnable = _Gscan + _Grunnable // 0x1001
    _Gscanrunning  = _Gscan + _Grunning  // 0x1002
    _Gscansyscall  = _Gscan + _Gsyscall  // 0x1003
    _Gscanwaiting  = _Gscan + _Gwaiting  // 0x1004
)
```

接下来是P的状态。P的状态相比G来说，简单许多。

```go
const (
    // P status
    // 当M发现无待运行的G时会进入休眠, 这时M拥有的P会变为空闲并加到空闲P链表中
    _Pidle    = iota
    // 当M拥有了一个P后, 这个P的状态就会变为运行中, M运行G会使用这个P中的资源
    _Prunning // Only this P is allowed to change from _Prunning.
    // 当go调用原生代码, 原生代码又反过来调用go代码时, 使用的P会变为此状态
    _Psyscall
    // 当gc停止了整个世界(STW)时, P会变为此状态
    _Pgcstop
    // 当P的数量在运行时改变, 且数量减少时多余的P会变为此状态
    _Pdead
)
```

M本身没有像P、G一样的状态定义，但是感兴趣的话，可以查看一下M的结构体定义来进行了解。

### 协程(Goroutine scheduler)的源码实现

接下来，我们就来看下go rutime的启动过程，源码位置 [src/runtime/proc.go](https://github.com/golang/go/blob/master/src/runtime/proc.go)

go 的运行时包含了一些汇编的内容，在源码中有汇编代码，我们可以自编译。也可以使用GDB调试来实验go的运行时，这里我们暂时不深入。所以，如果要看goroutine创建的话，一定要结合GDB调试。补充一点：go在build的时候，是把自己的运行时也一起build到bin中了，所以go的可执行文件一般比较大。

首先是main goroutine

```go
// The main goroutine.
func main() {
    // 获取一个G,一般就是main
    g := getg()

    // Racectx of m0->g0 is used only as the parent of the main goroutine.
    // It must not be used for anything else.
    g.m.g0.racectx = 0

    //
    // Max stack size is 1 GB on 64-bit, 250 MB on 32-bit.
    // Using decimal instead of binary GB and MB because
    // they look nicer in the stack overflow failure message.
    if sys.PtrSize == 8 {
        maxstacksize = 1000000000
    } else {
        maxstacksize = 250000000
    }

    // Allow newproc to start new Ms.
    mainStarted = true

    if GOARCH != "wasm" { // no threads on wasm yet, so no sysmon
        systemstack(func() {
            newm(sysmon, nil)
        })
    }

    // Lock the main goroutine onto this, the main OS thread,
    // during initialization. Most programs won't care, but a few
    // do require certain calls to be made by the main thread.
    // Those can arrange for main.main to run in the main thread
    // by calling runtime.LockOSThread during initialization
    // to preserve the lock.
    lockOSThread()

    if g.m != &m0 {
        throw("runtime.main not on m0")
    }

    doInit(&runtime_inittask) // must be before defer
    if nanotime() == 0 {
        throw("nanotime returning zero")
    }

    // Defer unlock so that runtime.Goexit during init does the unlock too.
    needUnlock := true
    defer func() {
        if needUnlock {
            unlockOSThread()
        }
    }()

    // Record when the world started.
    runtimeInitTime = nanotime()

    // 开启GC
    gcenable()

    main_init_done = make(chan bool)
    // 进行一些检查
    if iscgo {
        if _cgo_thread_start == nil {
            throw("_cgo_thread_start missing")
        }
        if GOOS != "windows" {
            if _cgo_setenv == nil {
                throw("_cgo_setenv missing")
            }
            if _cgo_unsetenv == nil {
                throw("_cgo_unsetenv missing")
            }
        }
        if _cgo_notify_runtime_init_done == nil {
            throw("_cgo_notify_runtime_init_done missing")
        }
        // Start the template thread in case we enter Go from
        // a C-created thread and need to create a new thread.
        startTemplateThread()
        cgocall(_cgo_notify_runtime_init_done, nil)
    }

    doInit(&main_inittask)

    close(main_init_done)

    needUnlock = false
    unlockOSThread()

    if isarchive || islibrary {
        // A program compiled with -buildmode=c-archive or c-shared
        // has a main, but it is not executed.
        return
    }
    fn := main_main // make an indirect call, as the linker doesn't know the address of the main package when laying down the runtime
    fn()
    // 调试的
    if raceenabled {
        racefini()
    }

    // Make racy client program work: if panicking on
    // another goroutine at the same time as main returns,
    // let the other goroutine finish printing the panic trace.
    // Once it does, it will exit. See issues 3934 and 20018.
    if atomic.Load(&runningPanicDefers) != 0 {
        // Running deferred functions should not take long.
        for c := 0; c < 1000; c++ {
            if atomic.Load(&runningPanicDefers) == 0 {
                break
            }
            Gosched()
        }
    }
    if atomic.Load(&panicking) != 0 {
        gopark(nil, nil, waitReasonPanicWait, traceEvGoStop, 1)
    }

    exit(0)
    for {
        var x *int32
        *x = 0
    }
}
```

如何创建一个goroutine呢？

```go
// Create a new g running fn with siz bytes of arguments.
// Put it on the queue of g's waiting to run.
// The compiler turns a go statement into a call to this.
// Cannot split the stack because it assumes that the arguments
// are available sequentially after &fn; they would not be
// copied if a stack split occurred.
//go:nosplit
func newproc(siz int32, fn *funcval) {
    argp := add(unsafe.Pointer(&fn), sys.PtrSize)
    gp := getg()
    pc := getcallerpc()
    systemstack(func() {
        newproc1(fn, (*uint8)(argp), siz, gp, pc)
    })
}

// Create a new g running fn with narg bytes of arguments starting
// at argp. callerpc is the address of the go statement that created
// this. The new g is put on the queue of g's waiting to run.
func newproc1(fn *funcval, argp *uint8, narg int32, callergp *g, callerpc uintptr) {
    _g_ := getg()

    if fn == nil {
        _g_.m.throwing = -1 // do not dump full stacks
        throw("go of nil func value")
    }
    acquirem() // disable preemption because it can be holding p in a local var
    siz := narg
    siz = (siz + 7) &^ 7

    // We could allocate a larger initial stack if necessary.
    // Not worth it: this is almost always an error.
    // 4*sizeof(uintreg): extra space added below
    // sizeof(uintreg): caller's LR (arm) or return address (x86, in gostartcall).
    if siz >= _StackMin-4*sys.RegSize-sys.RegSize {
        throw("newproc: function arguments too large for new goroutine")
    }

    _p_ := _g_.m.p.ptr()
    newg := gfget(_p_)
    if newg == nil {
        newg = malg(_StackMin)
        casgstatus(newg, _Gidle, _Gdead)
        allgadd(newg) // publishes with a g->status of Gdead so GC scanner doesn't look at uninitialized stack.
    }
    if newg.stack.hi == 0 {
        throw("newproc1: newg missing stack")
    }

    if readgstatus(newg) != _Gdead {
        throw("newproc1: new g is not Gdead")
    }

    totalSize := 4*sys.RegSize + uintptr(siz) + sys.MinFrameSize // extra space in case of reads slightly beyond frame
    totalSize += -totalSize & (sys.SpAlign - 1)                  // align to spAlign
    sp := newg.stack.hi - totalSize
    spArg := sp
    if usesLR {
        // caller's LR
        *(*uintptr)(unsafe.Pointer(sp)) = 0
        prepGoExitFrame(sp)
        spArg += sys.MinFrameSize
    }
    if narg > 0 {
        memmove(unsafe.Pointer(spArg), unsafe.Pointer(argp), uintptr(narg))
        // This is a stack-to-stack copy. If write barriers
        // are enabled and the source stack is grey (the
        // destination is always black), then perform a
        // barrier copy. We do this *after* the memmove
        // because the destination stack may have garbage on
        // it.
        if writeBarrier.needed && !_g_.m.curg.gcscandone {
            f := findfunc(fn.fn)
            stkmap := (*stackmap)(funcdata(f, _FUNCDATA_ArgsPointerMaps))
            if stkmap.nbit > 0 {
                // We're in the prologue, so it's always stack map index 0.
                bv := stackmapdata(stkmap, 0)
                bulkBarrierBitmap(spArg, spArg, uintptr(bv.n)*sys.PtrSize, 0, bv.bytedata)
            }
        }
    }

    memclrNoHeapPointers(unsafe.Pointer(&newg.sched), unsafe.Sizeof(newg.sched))
    newg.sched.sp = sp
    newg.stktopsp = sp
    newg.sched.pc = funcPC(goexit) + sys.PCQuantum // +PCQuantum so that previous instruction is in same function
    newg.sched.g = guintptr(unsafe.Pointer(newg))
    gostartcallfn(&newg.sched, fn)
    newg.gopc = callerpc
    newg.ancestors = saveAncestors(callergp)
    newg.startpc = fn.fn
    if _g_.m.curg != nil {
        newg.labels = _g_.m.curg.labels
    }
    if isSystemGoroutine(newg, false) {
        atomic.Xadd(&sched.ngsys, +1)
    }
    newg.gcscanvalid = false
    casgstatus(newg, _Gdead, _Grunnable)

    if _p_.goidcache == _p_.goidcacheend {
        // Sched.goidgen is the last allocated id,
        // this batch must be [sched.goidgen+1, sched.goidgen+GoidCacheBatch].
        // At startup sched.goidgen=0, so main goroutine receives goid=1.
        _p_.goidcache = atomic.Xadd64(&sched.goidgen, _GoidCacheBatch)
        _p_.goidcache -= _GoidCacheBatch - 1
        _p_.goidcacheend = _p_.goidcache + _GoidCacheBatch
    }
    newg.goid = int64(_p_.goidcache)
    _p_.goidcache++
    if raceenabled {
        newg.racectx = racegostart(callerpc)
    }
    if trace.enabled {
        traceGoCreate(newg, newg.startpc)
    }
    runqput(_p_, newg, true)

    if atomic.Load(&sched.npidle) != 0 && atomic.Load(&sched.nmspinning) == 0 && mainStarted {
        wakep()
    }
    releasem(_g_.m)
}
```

## 一些参考

* [goroutine背后的知识](http://www.sizeofvoid.net/goroutine-under-the-hood/)
* [Go的并发哲学](https://www.kancloud.cn/mutouzhang/go/596822)
* [深入浅出java多线程](https://redspider.gitbook.io/concurrent/di-yi-pian-ji-chu-pian/1)
* [协程的实现原理](https://www.cnblogs.com/zkweb/p/7815600.html)


# Channel

* [Channel](/golang/source/channel#channel)
  * [提出问题](https://www.selinux.tech/golang/source/pages/-LckHuHsBKLs6kefsCSK#提出问题)
  * [Channel定义](<https://www.selinux.tech/golang/source/pages/-LckHuHsBKLs6kefsCSK#channel定义>)
  * [make channel](/golang/source/channel#make-channel)
  * [chansend](/golang/source/channel#chansend)
  * [chanrecv](/golang/source/channel#chanrecv)
  * [close](/golang/source/channel#close)
  * [参考链接](https://www.selinux.tech/golang/source/pages/-LckHuHsBKLs6kefsCSK#参考链接)

## 提出问题

经过自己很长一段时间的实验，发现带着问题来阅读源码是非常高效的，因为源码一般大而杂，分支也比较多，稍不留神就容易走进细枝末节跳不出来了。所以我们这里先抛出几个问题，然后带着问题一起去源码中寻找答案。

* channel的数据结构是怎么，内部又包含了哪些组件
* goroutine向channel中发送数据的阻塞机制是什么
* 有缓冲和无缓冲在实现过程中有哪些区别
* channel-safe的实现机制是什么
* write-to-channel 与 read-from-channel 是怎么协作的
* channel在关闭之后还可以做到能读不能写，那关闭时需要注意什么

接下来我们将一些比较重要的源码，以及源码分析过程中的备注都贴到下方。

## Channel定义

源码位置 [/src/runtime/chan.go](https://github.com/golang/go/blob/master/src/runtime/chan.go)

```go
type hchan struct {
    qcount   uint           // qcount 是 buf 中已塞进的元素数量,当前队列中的元素数量
    dataqsiz uint           // 队列可以容纳的元素数量, 如果为0表示这个channel无缓冲区，就是make(chan,size)中的size
    buf      unsafe.Pointer // 队列的缓冲区, 结构是环形队列
    elemsize uint16         //channel 中数据类型的大小
    closed   uint32         // 表示 channel 是否关闭
    elemtype *_type         // 元素的类型, 判断是否调用写屏障时使用
    sendx    uint           // 发送元素的序号
    recvx    uint           // 接收元素的序号
    recvq    waitq          // 当前等待从channel接收数据的G的链表(实际类型是sudog的链表),由 recv 行为（也就是 <-ch）阻塞在 channel 上的 goroutine 队列
    sendq    waitq          // 当前等待发送数据到channel的G的链表(实际类型是sudog的链表),由 send 行为 (也就是 ch<-) 阻塞在 channel 上的 goroutine 队列

    // lock protects all fields in hchan, as well as several
    // fields in sudogs blocked on this channel.
    //
    // Do not change another G's status while holding this lock
    // (in particular, do not ready a G), as this can deadlock
    // with stack shrinking.
    lock mutex
}

type waitq struct {
    first *sudog
    last  *sudog
}
```

channel 的实现，很明显是使用队列来完成。

大家都知道channel是线程安全的，那么它是怎么实现的呢？答案很简单就是锁机制。

sudog 源码位置 [src/runtime/runtime2.go](https://github.com/golang/go/blob/master/src/runtime/runtime2.go)\
sudog 是一种非常重要的数据结构，相当于我们前面介绍的G。

```go
// sudog represents a g in a wait list, such as for sending/receiving
// on a channel.
// sudog 表示一个在 wait list 中的g
// sudog is necessary because the g ↔ synchronization object relation
// is many-to-many.
// A g can be on many wait lists, so there may be
// many sudogs for one g;
//
// and many gs may be waiting on the same
// synchronization object, so there may be many sudogs for one object.
//
// sudogs are allocated from a special pool. Use acquireSudog and
// releaseSudog to allocate and free them.
type sudog struct {
    // The following fields are protected by the hchan.lock of the
    // channel this sudog is blocking on. shrinkstack depends on
    // this for sudogs involved in channel ops.

    g *g

    // isSelect indicates g is participating in a select, so
    // g.selectDone must be CAS'd to win the wake-up race.
    isSelect bool
    next     *sudog
    prev     *sudog
    elem     unsafe.Pointer // data element (may point to stack)

    // The following fields are never accessed concurrently.
    // For channels, waitlink is only accessed by g.
    // For semaphores, all fields (including the ones above)
    // are only accessed when holding a semaRoot lock.

    acquiretime int64
    releasetime int64
    ticket      uint32
    parent      *sudog // semaRoot binary tree
    waitlink    *sudog // g.waiting list or semaRoot
    waittail    *sudog // semaRoot
    c           *hchan // channel
}
```

## make channel

创建一个go channel

```go
//go:linkname reflect_makechan reflect.makechan
func reflect_makechan(t *chantype, size int) *hchan {
    return makechan(t, size)
}

func makechan64(t *chantype, size int64) *hchan {
    if int64(int(size)) != size {
        panic(plainError("makechan: size out of range"))
    }

    return makechan(t, int(size))
}

// 创建一个go channel
func makechan(t *chantype, size int) *hchan {
    // 传入的数据类型
    elem := t.elem

    // compiler checks this but be safe.
    if elem.size >= 1<<16 {
        throw("makechan: invalid channel element type")
    }
    if hchanSize%maxAlign != 0 || elem.align > maxAlign {
        throw("makechan: bad alignment")
    }
    // 根据传入的数据类型占用的地址空间大小和缓存个数计算一个内存地址
    mem, overflow := math.MulUintptr(elem.size, uintptr(size))
    if overflow || mem > maxAlloc-hchanSize || size < 0 {
        panic(plainError("makechan: size out of range"))
    }

    // Hchan does not contain pointers interesting for GC when elements stored in buf do not contain pointers.
    // buf points into the same allocation, elemtype is persistent.
    // SudoG's are referenced from their owning thread so they can't be collected.
    // TODO(dvyukov,rlh): Rethink when collector can move allocated objects.
    var c *hchan
    switch {
    case mem == 0:
        // Queue or element size is zero.
        c = (*hchan)(mallocgc(hchanSize, nil, true))
        // Race detector uses this location for synchronization.
        c.buf = c.raceaddr()
    case elem.ptrdata == 0:
        // Elements do not contain pointers.
        // Allocate hchan and buf in one call.
        c = (*hchan)(mallocgc(hchanSize+mem, nil, true))
        c.buf = add(unsafe.Pointer(c), hchanSize)
    default:
        // Elements contain pointers.
        c = new(hchan)
        // 根据channel中存储的数据类型，分配的一段内存空间
        c.buf = mallocgc(mem, elem, true)
    }

    c.elemsize = uint16(elem.size)
    c.elemtype = elem
    // channel 缓存大小
    c.dataqsiz = uint(size)

    if debugChan {
        print("makechan: chan=", c, "; elemsize=", elem.size, "; elemalg=", elem.alg, "; dataqsiz=", size, "\n")
    }
    return c
}
```

makechan 这里有几个需要注意的点。

* 根据channel中传递的元素类型(`makechan(type,size)`)，计算出一块内存大小，后期创建一块内存区域, 对应代码是

```go
mem, overflow := math.MulUintptr(elem.size, uintptr(size))
```

* `qcount` 记录的是buf 中的数据数量，后面会有增减。`dataqsiz` 记录的是 `makechan(type,size)` 中的size，不会变。要记住，要不然后面会混。

## chansend

向channel中发送数据。

```go
// channel send 数据的入口
// entry point for c <- x from compiled code
//go:nosplit
func chansend1(c *hchan, elem unsafe.Pointer) {
    chansend(c, elem, true, getcallerpc())
}

/*
 * generic single channel send/recv
 * If block is not nil,
 * then the protocol will not
 * sleep but return if it could
 * not complete.
 *
 * sleep can wake up with g.param == nil
 * when a channel involved in the sleep has
 * been closed.  it is easiest to loop and re-run
 * the operation; we'll see that it's now closed.
 */
func chansend(c *hchan, ep unsafe.Pointer, block bool, callerpc uintptr) bool {
    // 应用层的 channel 为空
    // 例如 var a chan int
    // a<-1
    if c == nil {
        if !block {
            return false
        }
        // nil channel 发送数据会永远阻塞下去
        // PS：注意，会发生 panic 那种情况是 channel 被 closed 了，不是 nil channel
        // 挂起当前 goroutine
        gopark(nil, nil, waitReasonChanSendNilChan, traceEvGoStop, 2)
        throw("unreachable")
    }

    // 开发调试时使用，不用管
    if debugChan {
        print("chansend: chan=", c, "\n")
    }
    // 调试的，可忽略
    if raceenabled {
        racereadpc(c.raceaddr(), callerpc, funcPC(chansend))
    }

    // Fast path: check for failed non-blocking operation without acquiring the lock.
    // 检查失败的非阻塞操作而不获取锁定。
    //
    // After observing that the channel is not closed, we observe that the channel is
    // not ready for sending.
    // 在观察到通道未关闭后，我们发现通道尚未准备好发送。
    // Each of these observations is a single word-sized read
    // (first c.closed and second c.recvq.first or c.qcount depending on kind of channel).

    // 这些观察中的每一个都是单个字大小的读取（第一个c.closed和第二个c.recvq.first或c.qcount，取决于通道的类型）。
    // Because a closed channel cannot transition(转换) from 'ready for sending' to
    // 'not ready for sending', even if the channel is closed between the two observations,
    // they imply(意味着) a moment between the two when the channel was both not yet closed
    // and not ready for sending. We behave as if we observed the channel at that moment,
    // and report that the send cannot proceed.
    //
    //
    // It is okay if the reads are reordered here: if we observe that the channel is not
    // ready for sending and then observe that it is not closed, that implies that the
    // channel wasn't closed during the first observation.

    if !block && c.closed == 0 && ((c.dataqsiz == 0 && c.recvq.first == nil) ||
        (c.dataqsiz > 0 && c.qcount == c.dataqsiz)) {
        return false
    }

    var t0 int64
    if blockprofilerate > 0 {
        t0 = cputicks()
    }

    // 加锁了所以并发安全
    lock(&c.lock)

    // channel 已被关闭，panic
    if c.closed != 0 {
        unlock(&c.lock)
        panic(plainError("send on closed channel"))
    }

    // 这是一个出队列的操作
    // 一单这里进去了，说明buf已空
    if sg := c.recvq.dequeue(); sg != nil {
        // 寻找一个等待中的 receiver
        // 越过 channel 的 buffer
        // 直接把要发的数据拷贝给这个 receiver
        // 然后就返
        // 将锁也一并传递给了send
        // Found a waiting receiver. We pass the value we want to send
        // directly to the receiver, bypassing the channel buffer (if any).
        send(c, sg, ep, func() { unlock(&c.lock) }, 3)
        return true
    }

    // qcount 是 buffer 中已塞进的元素数量
    // dataqsize 是 buffer 的总大小
    // 说明还有余量
    if c.qcount < c.dataqsiz {
        // 存入内存buf
        // Space is available in the channel buffer. Enqueue the element to send.
        qp := chanbuf(c, c.sendx)
        if raceenabled {
            raceacquire(qp)
            racerelease(qp)
        }
        // 将 goroutine 的数据拷贝到 buffer 中
        typedmemmove(c.elemtype, qp, ep)
        c.sendx++
        // 环形队列，所以如果已经加到最大了，就回 0
        if c.sendx == c.dataqsiz {
            c.sendx = 0
        }
        // 将 buffer 的元素计数 +1
        c.qcount++
        unlock(&c.lock)
        return true
    }

    if !block {
        unlock(&c.lock)
        return false
    }

    // Block on the channel. Some receiver will complete our operation for us.
    // 在 channel 上阻塞，receiver 会帮我们完成后续的工作
    gp := getg()
    mysg := acquireSudog()
    mysg.releasetime = 0
    if t0 != 0 {
        mysg.releasetime = -1
    }
    // No stack splits between assigning elem and enqueuing mysg
    // on gp.waiting where copystack can find it.
    mysg.elem = ep
    mysg.waitlink = nil
    mysg.g = gp
    mysg.isSelect = false
    mysg.c = c
    gp.waiting = mysg
    gp.param = nil
    // 将当前这个发送 goroutine 打包后的 sudog 入队到 channel 的 sendq 队列中
    // 进入队列
    c.sendq.enqueue(mysg)

    // 将这个发送 g 从 Grunning -> Gwaiting
    // 进入休眠
    goparkunlock(&c.lock, waitReasonChanSend, traceEvGoBlockSend, 3)
    // Ensure the value being sent is kept alive until the
    // receiver copies it out. The sudog has a pointer to the
    // stack object, but sudogs aren't considered as roots of the
    // stack tracer.
    KeepAlive(ep)

    // someone woke us up.
    // 这里是被唤醒后要执行的代码
    if mysg != gp.waiting {
        // 先判断当前是不是合法的休眠中
        throw("G waiting list is corrupted")
    }
    gp.waiting = nil
    if gp.param == nil {
        if c.closed == 0 {
            throw("chansend: spurious wakeup")
        }
        // 唤醒后发现 channel 被人关了，panic
        panic(plainError("send on closed channel"))
    }
    gp.param = nil
    if mysg.releasetime > 0 {
        blockevent(mysg.releasetime-t0, 2)
    }
    mysg.c = nil
    releaseSudog(mysg)
    return true
}

// send processes a send operation on an empty channel c.
// The value ep sent by the sender is copied to the receiver sg.
// The receiver is then woken up to go on its merry way.
// Channel c must be empty and locked.  send unlocks c with unlockf.
// sg must already be dequeued from c.
// ep must be non-nil and point to the heap or the caller's stack.
func send(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {
    //忽略
    if raceenabled {
        if c.dataqsiz == 0 {
            racesync(c, sg)
        } else {
            // Pretend we go through the buffer, even though
            // we copy directly. Note that we need to increment
            // the head/tail locations only when raceenabled.
            qp := chanbuf(c, c.recvx)
            raceacquire(qp)
            racerelease(qp)
            raceacquireg(sg.g, qp)
            racereleaseg(sg.g, qp)
            c.recvx++
            if c.recvx == c.dataqsiz {
                c.recvx = 0
            }
            c.sendx = c.recvx // c.sendx = (c.sendx+1) % c.dataqsiz
        }
    }

    // receiver 的 sudog 已经在对应区域分配过空间
    // 我们只要把数据拷贝过去
    if sg.elem != nil {
        sendDirect(c.elemtype, sg, ep)
        sg.elem = nil
    }
    gp := sg.g
    unlockf()
    gp.param = unsafe.Pointer(sg)
    if sg.releasetime != 0 {
        sg.releasetime = cputicks()
    }
    // Gwaiting -> Grunnable
    goready(gp, skip+1)
}
```

前面我们提到过，channel线程安全的原因就是有锁机制，所以向channel中写入数据的话，首先要获取锁，然后发送数据，发送完成之后，释放锁。

通过代码我们能看出，发送数据时，首先会查看recvq中是否有是否有goroutine在等待，如果有直接讲数据发送给他。

发送数据到channel实际调用的是runtime.chansend1函数, chansend1函数调用了chansend函数, 流程是:

> 下面的文字引用自 [协程的实现原理](https://www.cnblogs.com/zkweb/p/7815600.html)

* 检查channel.recvq是否有等待中的接收者的G
  * 如果有, 表示channel无缓冲区或者缓冲区为空
  * 调用send函数
    * 如果sudog.elem不等于nil, 调用sendDirect函数从发送者直接复制元素
    * 等待接收的sudog.elem是指向接收目标的内存的指针, 如果是接收目标是\_则elem是nil, 可以省略复制
    * 等待发送的sudog.elem是指向来源目标的内存的指针
    * 复制后调用goready恢复发送者的G
      * 切换到g0调用ready函数, 调用完切换回来
        * 把G的状态由等待中(\_Gwaiting)改为待运行(\_Grunnable)
        * 把G放到P的本地运行队列
        * 如果当前有空闲的P, 但是无自旋的M(nmspinning等于0), 则唤醒或新建一个M
  * 从发送者拿到数据并唤醒了G后, 就可以从chansend返回了
* 判断是否可以把元素放到缓冲区中
  * 如果缓冲区有空余的空间, 则把元素放到缓冲区并从chansend返回
* 无缓冲区或缓冲区已经写满, 发送者的G需要等待
  * 获取当前的g
  * 新建一个sudog
  * 设置sudog.elem = 指向发送内存的指针
  * 设置sudog.g = g
  * 设置sudog.c = channel
  * 设置g.waiting = sudog
  * 把sudog放入channel.sendq
  * 调用goparkunlock函数
    * 调用gopark函数
      * 通过mcall函数调用park\_m函数
        * mcall函数和上面说明的一样, 会把当前的状态保存到g.sched, 然后切换到g0和g0的栈空间并执行指定的函数
        * park\_m函数首先把G的状态从运行中(\_Grunning)改为等待中(\_Gwaiting)
        * 然后调用dropg函数解除M和G之间的关联
        * 再调用传入的解锁函数, 这里的解锁函数会对解除channel.lock的锁定
        * 最后调用schedule函数继续调度
* 从这里恢复表示已经成功发送或者channel已关闭
  * 检查sudog.param是否为nil, 如果为nil表示channel已关闭, 抛出panic
  * 否则释放sudog然后返回

## chanrecv

与发送数据一样，读取数据也要首先获取锁，然后才能读取。

```go
// entry points for <- c from compiled code
//go:nosplit
func chanrecv1(c *hchan, elem unsafe.Pointer) {
    chanrecv(c, elem, true)
}

//go:nosplit
func chanrecv2(c *hchan, elem unsafe.Pointer) (received bool) {
    _, received = chanrecv(c, elem, true)
    return
}

// chanrecv receives on channel c and writes the received data to ep.
// ep may be nil, in which case received data is ignored.
// If block == false and no elements are available, returns (false, false).
// Otherwise, if c is closed, zeros *ep and returns (true, false).
// Otherwise, fills in *ep with an element and returns (true, true).
// A non-nil ep must point to the heap or the caller's stack.
func chanrecv(c *hchan, ep unsafe.Pointer, block bool) (selected, received bool) {
    // raceenabled: don't need to check ep, as it is always on the stack
    // or is new memory allocated by reflect.

    if debugChan {
        print("chanrecv: chan=", c, "\n")
    }
    // 如果在 nil channel 上进行 recv 操作，那么会永远阻塞
    if c == nil {
        if !block {
            // 非阻塞的情况下
            // 要直接返回
            // 非阻塞出现在一些 select 的场景中
            // 参见 selectnbrecv/selectnbrecv2
            return
        }
        // 当前 goroutine: Grunning -> Gwaiting
        // 其实就是该 goroutine 直接泄露 leak 了
        gopark(nil, nil, waitReasonChanReceiveNilChan, traceEvGoStop, 2)
        throw("unreachable")
    }

    // Fast path: check for failed non-blocking operation without acquiring the lock.
    //
    // After observing that the channel is not ready for receiving, we observe that the
    // channel is not closed. Each of these observations is a single word-sized read
    // (first c.sendq.first or c.qcount, and second c.closed).
    // Because a channel cannot be reopened, the later observation of the channel
    // being not closed implies that it was also not closed at the moment of the
    // first observation. We behave as if we observed the channel at that moment
    // and report that the receive cannot proceed.
    //
    // The order of operations is important here: reversing the operations can lead to
    // incorrect behavior when racing with a close.
    if !block && (c.dataqsiz == 0 && c.sendq.first == nil ||
        c.dataqsiz > 0 && atomic.Loaduint(&c.qcount) == 0) &&
        atomic.Load(&c.closed) == 0 {
        // 非阻塞且没内容可收的情况下要直接返回
        // 两个 bool 的零值就是 false，false
        return
    }

    var t0 int64
    if blockprofilerate > 0 {
        t0 = cputicks()
    }

    lock(&c.lock)

    // 当前 channel 中没有数据可读
    // 直接返回 not selected
    if c.closed != 0 && c.qcount == 0 {
        if raceenabled {
            raceacquire(c.raceaddr())
        }
        unlock(&c.lock)
        if ep != nil {
            typedmemclr(c.elemtype, ep)
        }
        return true, false
    }

    // sender 队列中有 sudog 在等待
    // 直接从该 sudog 中获取数据拷贝到当前 g 即可
    // 一旦执行到这里，说明buf已满
    if sg := c.sendq.dequeue(); sg != nil {
        // Found a waiting sender. If buffer is size 0, receive value
        // directly from sender. Otherwise, receive from head of queue
        // and add sender's value to the tail of the queue (both map to
        // the same buffer slot because the queue is full).
        recv(c, sg, ep, func() { unlock(&c.lock) }, 3)
        return true, true
    }

    if c.qcount > 0 {
        // Receive directly from queue
        qp := chanbuf(c, c.recvx)
        if raceenabled {
            raceacquire(qp)
            racerelease(qp)
        }
        // 直接从 buffer 里拷贝数据
        if ep != nil {
            typedmemmove(c.elemtype, ep, qp)
        }
        typedmemclr(c.elemtype, qp)
        c.recvx++
        if c.recvx == c.dataqsiz {
            c.recvx = 0
        }
        c.qcount--
        unlock(&c.lock)
        return true, true
    }

    if !block {
        unlock(&c.lock)
        // 非阻塞时，且无数据可收
        // 始终不选中，这是在 buffer 中没内容的时候
        return false, false
    }

    // no sender available: block on this channel.
    gp := getg()
    mysg := acquireSudog()
    mysg.releasetime = 0
    if t0 != 0 {
        mysg.releasetime = -1
    }
    // No stack splits between assigning elem and enqueuing mysg
    // on gp.waiting where copystack can find it.
    mysg.elem = ep
    mysg.waitlink = nil
    gp.waiting = mysg
    mysg.g = gp
    mysg.isSelect = false
    mysg.c = c
    gp.param = nil
    c.recvq.enqueue(mysg)
    goparkunlock(&c.lock, waitReasonChanReceive, traceEvGoBlockRecv, 3)

    // someone woke us up
    if mysg != gp.waiting {
        throw("G waiting list is corrupted")
    }
    gp.waiting = nil
    if mysg.releasetime > 0 {
        blockevent(mysg.releasetime-t0, 2)
    }
    closed := gp.param == nil
    gp.param = nil
    mysg.c = nil
    releaseSudog(mysg)
    // 如果 channel 未被关闭，那就是真的 recv 到数据了
    return true, !closed
}

// recv processes a receive operation on a full channel c.
// There are 2 parts:
// 1) The value sent by the sender sg is put into the channel
//    and the sender is woken up to go on its merry way.
// 2) The value received by the receiver (the current G) is
//    written to ep.
// For synchronous channels, both values are the same.
// For asynchronous channels, the receiver gets its data from
// the channel buffer and the sender's data is put in the
// channel buffer.
// Channel c must be full and locked. recv unlocks c with unlockf.
// sg must already be dequeued from c.
// A non-nil ep must point to the heap or the caller's stack.
func recv(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) {
    if c.dataqsiz == 0 {
        if raceenabled {
            racesync(c, sg)
        }
        if ep != nil {
            // copy data from sender
            recvDirect(c.elemtype, sg, ep)
        }
    } else {
        // Queue is full. Take the item at the
        // head of the queue. Make the sender enqueue
        // its item at the tail of the queue. Since the
        // queue is full, those are both the same slot.
        qp := chanbuf(c, c.recvx)
        if raceenabled {
            raceacquire(qp)
            racerelease(qp)
            raceacquireg(sg.g, qp)
            racereleaseg(sg.g, qp)
        }
        // copy data from queue to receiver
        if ep != nil {
            typedmemmove(c.elemtype, ep, qp)
        }
        // copy data from sender to queue
        typedmemmove(c.elemtype, qp, sg.elem)
        c.recvx++
        if c.recvx == c.dataqsiz {
            c.recvx = 0
        }
        c.sendx = c.recvx // c.sendx = (c.sendx+1) % c.dataqsiz
    }
    sg.elem = nil
    gp := sg.g
    unlockf()
    gp.param = unsafe.Pointer(sg)
    if sg.releasetime != 0 {
        sg.releasetime = cputicks()
    }
    goready(gp, skip+1)
}
```

**画重点:**

下面这段代码很重要，它解释了sendx和recvx的大小，最大recvx不会超过dataqsiz

```go
if c.recvx == c.dataqsiz {
    c.recvx = 0
}
c.sendx = c.recvx // c.sendx = (c.sendx+1) % c.dataqsiz
```

> 下面的文字引用自 [协程的实现原理](https://www.cnblogs.com/zkweb/p/7815600.html)

从channel接收数据实际调用的是runtime.chanrecv1函数, chanrecv1函数调用了chanrecv函数, 流程是:

* 检查channel.sendq中是否有等待中的发送者的G
  * 如果有, 表示channel无缓冲区或者缓冲区已满, 这两种情况需要分别处理(为了保证入出队顺序一致)
  * 调用recv函数
    * 如果无缓冲区, 调用recvDirect函数把元素直接复制给接收者
    * 如果有缓冲区代表缓冲区已满
      * 把队列中下一个要出队的元素直接复制给接收者
      * 把发送的元素复制到队列中刚才出队的位置
      * 这时候缓冲区仍然是满的, 但是发送序号和接收序号都会增加1
    * 复制后调用goready恢复接收者的G, 处理同上
  * 把数据交给接收者并唤醒了G后, 就可以从chanrecv返回了
* 判断是否可以从缓冲区获取元素
  * 如果缓冲区有元素, 则直接取出该元素并从chanrecv返回
* 无缓冲区或缓冲区无元素, 接收者的G需要等待
  * 获取当前的g
  * 新建一个sudog
  * 设置sudog.elem = 指向接收内存的指针
  * 设置sudog.g = g
  * 设置sudog.c = channel
  * 设置g.waiting = sudog
  * 把sudog放入channel.recvq
  * 调用goparkunlock函数, 处理同上
* 从这里恢复表示已经成功接收或者channel已关闭
  * 检查sudog.param是否为nil, 如果为nil表示channel已关闭
  * 和发送不一样的是接收不会抛panic, 会通过返回值通知channel已关闭
  * 释放sudog然后返回

## close

```go
func closechan(c *hchan) {
    // 关闭一个 nil channel 会直接 panic
    if c == nil {
        panic(plainError("close of nil channel"))
    }
    // 上锁，这个锁的粒度比较大，一直到释放完所有的 sudog 才解锁
    lock(&c.lock)
    // 在 close channel 时，如果 channel 已经关闭过了
    // 直接触发 panic
    if c.closed != 0 {
        unlock(&c.lock)
        panic(plainError("close of closed channel"))
    }

    // 忽略
    if raceenabled {
        callerpc := getcallerpc()
        racewritepc(c.raceaddr(), callerpc, funcPC(closechan))
        racerelease(c.raceaddr())
    }

    // 先讲 close 标志置为1
    c.closed = 1

    var glist gList

    // release all readers
    // 将所有的recvq 出队列
    for {
        sg := c.recvq.dequeue()
        // 弹出的 sudog 是 nil
        // 说明读队列已经空了
        if sg == nil {
            break
        }
        // sg.elem unsafe.Pointer，指向 sudog 的数据元素
        // 该元素可能在堆上分配，也可能在栈上
        if sg.elem != nil {
            // 释放对应的内存
            typedmemclr(c.elemtype, sg.elem)
            sg.elem = nil
        }
        if sg.releasetime != 0 {
            sg.releasetime = cputicks()
        }
        // 将 goroutine 入 glist
        // 为最后将全部 goroutine 都 ready 做准备
        gp := sg.g
        gp.param = nil
        if raceenabled {
            raceacquireg(gp, c.raceaddr())
        }
        glist.push(gp)
    }

    // release all writers (they will panic)
    // 将所有挂在 channel 上的 writer 从 sendq 中弹出
    // 该操作会使所有 writer panic
    for {
        sg := c.sendq.dequeue()
        if sg == nil {
            break
        }
        sg.elem = nil
        if sg.releasetime != 0 {
            sg.releasetime = cputicks()
        }
        // 将 goroutine 入 glist
        // 为最后将全部 goroutine 都 ready 做准备
        gp := sg.g
        gp.param = nil
        if raceenabled {
            raceacquireg(gp, c.raceaddr())
        }
        glist.push(gp)
    }
    // 在释放所有挂在 channel 上的读或写 sudog 时
    // 是一直在临界区的
    unlock(&c.lock)

    // Ready all Gs now that we've dropped the channel lock.
    for !glist.empty() {
        gp := glist.pop()
        gp.schedlink = 0
        goready(gp, 3)
    }
}
```

> 下面的文字引用自 [协程的实现原理](https://www.cnblogs.com/zkweb/p/7815600.html)

关闭channel实际调用的是closechan函数, 流程是:

* 设置channel.closed = 1
* 枚举channel.recvq, 清零它们sudog.elem, 设置sudog.param = nil
* 枚举channel.sendq, 设置sudog.elem = nil, 设置sudog.param = nil
* 调用goready函数恢复所有接收者和发送者的G

## 参考链接

* [协程的实现原理](https://www.cnblogs.com/zkweb/p/7815600.html)
* [Go Channel 源码剖析](http://legendtkl.com/categories/golang)
* [Channel 从使用到源码分析](https://github.com/cch123/golang-notes/blob/master/channel.md)
* [理解go channel](https://blog.lab99.org/post/golang-2017-10-04-video-understanding-channels.html#select)
* [Go Chanel 使用与原理 三](https://segmentfault.com/a/1190000018531960)


# gRPC


# 1、快速开始

* [Go Quick Start](/golang/grpc/quick-start#go-quick-start)
  * [先决条件](https://www.selinux.tech/golang/grpc/pages/-LgMx9-TGhalUxsdZKHX#先决条件)
    * [Go Version](/golang/grpc/quick-start#go-version)
    * [Install gRPC](/golang/grpc/quick-start#install-grpc)
    * [Install Protocol Buffers v3](/golang/grpc/quick-start#install-protocol-buffers-v3)
  * [Download the example](/golang/grpc/quick-start#download-the-example)
  * [Build the example](/golang/grpc/quick-start#build-the-example)
  * [Try it](/golang/grpc/quick-start#try-it)
  * [Update a gRPC service](/golang/grpc/quick-start#update-a-grpc-service)
  * [Generate gRPC code](/golang/grpc/quick-start#generate-grpc-code)
  * [Update and run the application](/golang/grpc/quick-start#update-and-run-the-application)
    * [Update the server](/golang/grpc/quick-start#update-the-server)
    * [Update the client](/golang/grpc/quick-start#update-the-client)
    * [Run](/golang/grpc/quick-start#run)
  * [NEXT](/golang/grpc/quick-start#next)

## 先决条件

### Go Version

gRPC 要求go的版本是1.6或者更高

```
go version
```

### Install gRPC

使用下面的命令去安装gRPC

```
go get -u google.golang.org/grpc
```

如果国内的网络环境不允许的话，可以点击 [FAQ](https://github.com/grpc/grpc-go#FAQ)，查看相应的解决办法。

### Install Protocol Buffers v3

安装用于生成gRPC服务代码的protoc编译器.最简单的版本就是从这个地址 <https://github.com/protocolbuffers/protobuf/releases> 下载预编译好的二进制包,选择对应的平台以及版本就可以了。

解压到自己特定的目录，然后配置环境变量。

接下来，为Go安装protoc插件。

```
go get -u github.com/golang/protobuf/protoc-gen-go
```

这时，编译后的protoc插件会位于`$GOPATH/bin`目录下。可以到该目录下确认是否存在。

## Download the example

前面我们下载的 gRPC `go get google.golang.org/grpc`，同样包含了gRPC的example，位于 `$GOPATH/src/google.golang.org/grpc/examples` 目录下。

## Build the example

切换到 example 目录下

```
cd $GOPATH/src/google.golang.org/grpc/examples/helloworld
```

gRPC服务在 `.proto` 文件中定义，该文件用于生成相应的`.pb.go`文件。 `.pb.go`文件是通过使用protoc 编译器编译 `.proto` 文件生成的。

出于演示Demo的目的, `helloworld.pb.go` 文件已经被生成了 (通过编译 `helloworld.proto` ), 位于 `$GOPATH/src/google.golang.org/grpc/examples/helloworld/helloworld`.

`helloworld.pb.go` 文件包含两部分内容:

* 生成client和server端的代码
* 用于填充，序列化和检索`HelloRequest`和`HelloReply`消息类型的代码

这是example的代码目录结构。

![grpc-example](/files/-LgMx9WN9avy4SGQ6Xwh)

## Try it

下面我们就来真枪实弹的跑一下代码看看。

进入到 example的目录 `$GOPATH/src/google.golang.org/grpc/examples/helloworld`,首先运行server端代码。

```
go run greeter_server/main.go
```

然后另外开启一个终端，运行client端代码。

```
go run greeter_client/main.go
```

此时如果运行没有出现问题的话。client 端会输出 `Greeting: Hello world` .

## Update a gRPC service

接下来，如果我要更新应用程序添加新的接口给客户端调用，应该怎么做呢？gRPC服务默认是使用 `protocol buffers` 来定义的。可以点击[什么是 gRPC?](/golang/grpc/what-grpc) 和 [gRPC基础](/golang/grpc/grpc-basic)这两篇文章来 查看如何在 `.proto` 文件中定义一个服务。现在我们只需要之道，服务器和客户端都有一个SayHello RPC方法，该方法从客户端获取HelloRequest参数并从服务器返回HelloReply.它的定义是下面的样子.

位于 `helloworld/helloworld.proto` 文件中。

```
// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}
```

接下来，我们给service 再加上一个方法。

编辑`helloworld/helloworld.proto` 文件，添加一个 `SayHelloAgain` 的方法，它与 `SayHello` 方法接收同样的参数，有同样的返回值。

```
// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}

  // Sends another greeting
  rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}
```

## Generate gRPC code

接下来，我们更新我们的gRPC代码，让我们的应用采用新的方法定义。

```
protoc -I helloworld/ helloworld/helloworld.proto --go_out=plugins=grpc:helloworld
```

这个命令会让`helloworld.pb.go`自动生成我们刚才的改变。

## Update and run the application

刚才我们已经自动生成了 server 和 client 代码。但是我们仍然需要手动编写和调用这个新的方法。

### Update the server

编辑 `greeter_server/main.go` 文件，添加下面的这样一个方法。

```go
func (s *server) SayHelloAgain(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
        return &pb.HelloReply{Message: "Hello again " + in.Name}, nil
}
```

### Update the client

编辑 `greeter_client/main.go` 在main函数中添加下面这样一段代码。

```go
r, err = c.SayHelloAgain(ctx, &pb.HelloRequest{Name: name})
if err != nil {
        log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.Message)
```

### Run

接下来就可以运行了。开启两个终端分别运行 server和client.运行结果如下所示。

```
go run greeter_client/main.go
2019/06/02 19:25:56 Greeting: Hello world
2019/06/02 19:25:56 Greeting: Hello again world
```

## NEXT

* [What is gRPC](/golang/grpc/what-grpc)
* [gRPC Concepts](/golang/grpc/grpc-concepts)


# 2、什么是gRPC

* [What is gRPC](/golang/grpc/what-grpc#what-is-grpc)
  * [什么是RPC？](https://www.selinux.tech/golang/grpc/pages/-LgMx9-ReXKdylESS8Je#什么是rpc)
  * [OverView](/golang/grpc/what-grpc#overview)
  * [Working with Protocol Buffers](/golang/grpc/what-grpc#working-with-protocol-buffers)

## 什么是RPC？

首先，什么是RPC？

RPC简称远程过程调用，是一个用于构建基于Client和Server分布式应用程序的技术。目前业界已经有了很多的框架能够用来构建基于RPC的分布式应用，例如SpringBoot，Dubbo和gRPC。

RPC 标准最早是由Bruce Jay Nelson 写的论文 [Implementing Remote Procedure Calls](http://www.cs.cmu.edu/~dga/15-712/F07/papers/birrell842.pdf)中提出的，后期的所有的RPC框架都是在这个标准模式的基础上构建出来的。

![RPC](/files/-LhJILhlhkUwLYWVmcai)

具体的执行过程就是下面这个样子

* 客户端发起一个远程调用,它实际上是调用本地的Client Stub
* Client Stub 将接受到的参数进行按照约定的协议规范进行编码，并封装到即将发送的Message中。
* Client Stub 将消息发送给RPC Runtime，然后通过网络将包 发送给Server端
* 服务器端的 RPCRuntime 收到请求后，交给提供方 Stub 进行解码，然后调用服务端的方法， 服务端执行方法，返回结果
* 服务端的处理结果 同样再经过Server Stub 打包，然后传递给RPC Runtime。
* 服务端的RPC Runtime再把数据通过网络发送给Client端。
* Client 端接收到消息，然后进行Unpack，处理

## OverView

在gRPC中，客户端应用程序可以直接调用不同计算机上的服务应用程序上的方法，就像它是本地对象一样，使我们可以更轻松地创建分布式应用程序和服务。 与许多RPC系统一样，gRPC基于定义服务的思想，指定可以使用其参数和返回类型远程调用的方法。 在服务器端,服务端实现此接口并运行gRPC服务来处理客户端调用。 在客户端，客户端有一个存根（在某些语言中称为客户端），它提供与服务器相同的方法。服务端与客户端一一对应。

![gRPC调用示意图](/files/-LgQOVjqMIO1RRa67Ekt)

gRPC客户端和服务器可以在各种环境中相互运行和通信 ,并且可以使用任何gRPC支持的语言编写。 因此,我们可以使用Go，Python或Ruby轻松创建Java中的gRPC服务器。 此外，最新的Google API将具有gRPC版本的接口，我们可以轻松地在应用程序中构建Google功能。

## Working with Protocol Buffers

gRPC 默认采用 protocol buffers 数据传输格式。protocol buffers 是google开发的一种能够将结构数据序列化的数据描述语言。

使用protocol buffers的第一步是要在扩展名为.proto的proto文件中定义序列化的数据的结构。 Protocol buffer 数据会被结构化成一个 `message`,而这个 `message` 其实就是一条包含了一些属性（name-value对）的记录。例如下面的例子。

```
message Person {
  string name = 1;
  int32 id = 2;
  bool has_ponycopter = 3;
}
```

一旦我们指定好了数据结构，我们就可以使用`protocol buffer` 编译器`protoc`根据我们的 proto 定义 生成相应数据访问类了。编程语言可以是我们喜欢的任意语言。 这会为我们定义的结构中的成员对象提供简单的访问方法，以及将整个结构序列化和反序列化的一些方法。

例如在 [Go Quick Start](/golang/grpc/quick-start)例子中，我们定义的数据结构。

```
// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}

  // Sends another greeting
  rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}
```

在经过 proto 编译生成之后，会有下面的一部分代码生成。从其中能够看到Getter方法以及一些序列化和反序列化的方法。

```go
// The request message containing the user's name.
type HelloRequest struct {
    Name                 string   `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
    XXX_NoUnkeyedLiteral struct{} `json:"-"`
    XXX_unrecognized     []byte   `json:"-"`
    XXX_sizecache        int32    `json:"-"`
}

func (m *HelloRequest) Reset()         { *m = HelloRequest{} }
func (m *HelloRequest) String() string { return proto.CompactTextString(m) }
func (*HelloRequest) ProtoMessage()    {}
func (*HelloRequest) Descriptor() ([]byte, []int) {
    return fileDescriptor_17b8c58d586b62f2, []int{0}
}

func (m *HelloRequest) XXX_Unmarshal(b []byte) error {
    return xxx_messageInfo_HelloRequest.Unmarshal(m, b)
}
func (m *HelloRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
    return xxx_messageInfo_HelloRequest.Marshal(b, m, deterministic)
}
func (m *HelloRequest) XXX_Merge(src proto.Message) {
    xxx_messageInfo_HelloRequest.Merge(m, src)
}
func (m *HelloRequest) XXX_Size() int {
    return xxx_messageInfo_HelloRequest.Size(m)
}
func (m *HelloRequest) XXX_DiscardUnknown() {
    xxx_messageInfo_HelloRequest.DiscardUnknown(m)
}

var xxx_messageInfo_HelloRequest proto.InternalMessageInfo

func (m *HelloRequest) GetName() string {
    if m != nil {
        return m.Name
    }
    return ""
}

// The response message containing the greetings
type HelloReply struct {
    Message              string   `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
    XXX_NoUnkeyedLiteral struct{} `json:"-"`
    XXX_unrecognized     []byte   `json:"-"`
    XXX_sizecache        int32    `json:"-"`
}

func (m *HelloReply) Reset()         { *m = HelloReply{} }
func (m *HelloReply) String() string { return proto.CompactTextString(m) }
func (*HelloReply) ProtoMessage()    {}
func (*HelloReply) Descriptor() ([]byte, []int) {
    return fileDescriptor_17b8c58d586b62f2, []int{1}
}

func (m *HelloReply) XXX_Unmarshal(b []byte) error {
    return xxx_messageInfo_HelloReply.Unmarshal(m, b)
}
func (m *HelloReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
    return xxx_messageInfo_HelloReply.Marshal(b, m, deterministic)
}
func (m *HelloReply) XXX_Merge(src proto.Message) {
    xxx_messageInfo_HelloReply.Merge(m, src)
}
func (m *HelloReply) XXX_Size() int {
    return xxx_messageInfo_HelloReply.Size(m)
}
func (m *HelloReply) XXX_DiscardUnknown() {
    xxx_messageInfo_HelloReply.DiscardUnknown(m)
}

var xxx_messageInfo_HelloReply proto.InternalMessageInfo

func (m *HelloReply) GetMessage() string {
    if m != nil {
        return m.Message
    }
    return ""
}
```


# 3、gRPC概念梳理

## gRPC Concepts

* [gRPC Concepts](/golang/grpc/grpc-concepts#grpc-concepts)
  * [Service definition](/golang/grpc/grpc-concepts#service-definition)
  * [Using the API surface](/golang/grpc/grpc-concepts#using-the-api-surface)
  * [Synchronous vs. asynchronous](/golang/grpc/grpc-concepts#synchronous-vs-asynchronous)
* [RPC life cycle](/golang/grpc/grpc-concepts#rpc-life-cycle)
  * [Unary RPC](/golang/grpc/grpc-concepts#unary-rpc)
  * [Server streaming RPC](/golang/grpc/grpc-concepts#server-streaming-rpc)
  * [Client streaming RPC](/golang/grpc/grpc-concepts#client-streaming-rpc)
  * [Bidirectional streaming RPC](/golang/grpc/grpc-concepts#bidirectional-streaming-rpc)
  * [Deadlines/Timeouts](/golang/grpc/grpc-concepts#deadlinestimeouts)
  * [RPC termination](/golang/grpc/grpc-concepts#rpc-termination)
  * [Cancelling RPCs](/golang/grpc/grpc-concepts#cancelling-rpcs)
  * [Metadata](/golang/grpc/grpc-concepts#metadata)
  * [Channels](/golang/grpc/grpc-concepts#channels)

接下来我们来介绍一下 gRPC中的一些核心概念。

### Service definition

与许多RPC系统一样，gRPC基于定义服务的思想，指定可以使用其参数和返回类型的远程调用的方法. 默认情况下，gRPC使用 `protocol buffers` 作为接口定义语言（IDL）来描述服务接口和消息结构。 如果需要，可以使用其他替代方案.

```
service HelloService {
  rpc SayHello (HelloRequest) returns (HelloResponse);
}

message HelloRequest {
  string greeting = 1;
}

message HelloResponse {
  string reply = 1;
}
```

gRPC 允许定义四种服务方法:

* 一元RPC(Unary RPCs)，客户端向服务器发送单个请求并返回单个响应，就像正常的函数调用一样。

```
rpc SayHello(HelloRequest) returns (HelloResponse){
}
```

* 服务端流式RPC（Server streaming RPCs ），客户端向服务器发送请求并获取流以读取消息序列。客户端从返回的流中读取，直到没有更多消息。 gRPC保证单个RPC调用中的消息排序。

```
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){
}
```

* 客户端流式RPC(Client streaming RPCs)，客户端再次使用提供的流写入一系列消息并将其发送到服务器.一旦客户端写完消息，它就等待服务器读取它们并返回它的响应。 gRPC再次保证在单个RPC调用中的消息排序。

```
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse) {
}
```

* 双向流式RPC(Bidirectional streaming RPCs)，双方使用读写流发送一系列消息。这两个流独立运行，因此客户端和服务器可以按照自己喜欢的顺序进行读写.例如，服务端可以在写入其响应之前等待接收所有客户端消息，或者它可以交替地读取消息然后写入消息，或者读取和写入的其他组合。两个流中的消息的顺序不会发生变化。

```
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse){
}
```

我们将在下面的RPC生命周期部分中更详细地介绍不同类型的RPC.

### Using the API surface

从 `.proto`文件中的服务定义开始,gRPC 提供了能够生成server端和client端 代码的 `protocol buffer`编译插件。gRPC用户通常在客户端调用这些API，并在服务器端实现相应的API。

* 在服务端，服务端实现服务定义中声明的方法，并运行gRPC服务来处理客户端调用，gRPC基础结构解码request，执行服务方法并对response进行编码。
* 在客户端，客户端有一个称为存根的本地对象（对于某些语言，首选术语是客户端），它实现与服务相同的方法.然后客户端就可以在本地对象上调用这些方法，将调用的参数包装在适当的`protocol buffer`消息类型中——gRPC负责向服务器发送请求并返回服务器的`protocol buffer`响应.

### Synchronous vs. asynchronous

同步RPC调用在没有接收到服务端发送回来的response之前一直保持阻塞，这也是RPC调用中最期望的。 另一方面，网络本质上是异步的，在许多情况下，能够在不阻塞当前线程的情况下启动RPC非常有用。

## RPC life cycle

现在让我们仔细看看当gRPC客户端调用gRPC服务方法时会发生什么.

### Unary RPC

首先让我们看一下最简单的RPC类型，客户端发送单个请求并返回单个响应.

* 客户端在本地存根/客户端对象上调用方法之后，将会通知服务端已使用此调用的客户端元数据，方法名称和指定的截止时间（如果适用）调用RPC。
* 然后，服务端可以立即发送回自己的初始元数据（必须在任何响应之前发送），或者等待客户端的请求消息 --- 首先发生的是特定于应用程序的消息。
* 一旦服务端接受到了客户端的请求消息，它就会做一些相应的工作来构建response。然后服务端会将response返回给客户端，连同状态码以及其他的一些可选的状态信息一齐返回。
* 如果状态OK，客户端接收到了服务端的Response，从而完成客户端调用。

### Server streaming RPC

Server streaming RPC 类似于我们的简单示例，除了服务器在获取客户端的请求消息后发回响应流。服务端会将处理后的信息以及状态码和其他的可选状态信息一齐返回，客户端接收到此次响应，完成这次调用。

### Client streaming RPC

客户端流式RPC也类似于我们的简单示例，除了客户端向服务器发送请求流而不是单个请求。通常在收到了所有的客户端请求之后（但也不一定），服务端返回了包含状态信息以及其他可选信息的reponse，这次调用就结束了。

### Bidirectional streaming RPC

在双向流式RPC中，调用再次由调用方法的客户端和接收客户端元数据，方法名称和截止时间的服务端启动。服务端可以选择发回其初始元数据或等待客户端开始发送请求。

接下来会发生什么取决于应用程序，因为客户端和服务器可以按任何顺序读写 - 流完全独立地运行 。例如 服务端可以一直等待直到已经接收到了所有的客户端发送的消息之后才开始写入响应，或者服务端和客户端可以一直 `ping-pong`:服务器获取请求，然后发回响应，然后客户端根据响应发送另一个请求，依此类推。

### Deadlines/Timeouts

gRPC 允许客户端指定RPC调用完成的截止/超时时间，超时之后就会被 `DEADLINE_EXCEEDED` 错误中断。在服务端，服务端可以查询特定RPC是否已超时，或者剩余多少时间来完成RPC。

如果指定截止/超时时间因语言而异。有的语言api是在 截止到某一时间(固定时间点)，有的语言是在某一时间区间。

### RPC termination

在gRPC中，客户端和服务端都对这次调用是否成功进行独立的判断，它们的结论可能不匹配。这意味着，例如，可以在服务端成功完成RPC（“我已经发送了所有响应！”），但在客户端失败（“我的截止日期后响应才到达！”）。在客户端发送完所有请求之前，服务端也可以决定这次调用已经完成。

### Cancelling RPCs

客户端或服务端可以随时取消RPC.取消即立即终止RPC，以便不再进行进一步的工作。它不是“撤消”：取消之前所做的更改将不会被回滚。

### Metadata

元数据一般是键值对的形式，表示特定的RPC调用信息。key 一般是 strings，value一般也是string，有时可能是二进制数据。元数据对gRPC本身是不透明的 - 它允许客户端提供与服务器调用相关的信息，反之亦然。

### Channels

gRPC通道提供与指定主机和端口上的gRPC服务器的连接，并在创建客户端存根（或某些语言中的“客户端”）时使用。客户端可以指定通道参数来修改gRPC的默认行为，例如打开和关闭消息压缩。通道具有状态，包括已连接和空闲。通道具有状态，包括已连接和空闲。

**以上就是gRPC的相关定义**


# 4、基于Golang的gRPC入门

* [gRPC Basics - Go](/golang/grpc/grpc-basic#grpc-basics---go)
  * [Example Code](/golang/grpc/grpc-basic#example-code)
  * [定义服务](https://www.selinux.tech/golang/grpc/pages/-LgMx9-UagQUaDRJoQv1#定义服务)
  * [生成server端和client 端代码](https://www.selinux.tech/golang/grpc/pages/-LgMx9-UagQUaDRJoQv1#生成server端和client-端代码)
  * [编写Server端](https://www.selinux.tech/golang/grpc/pages/-LgMx9-UagQUaDRJoQv1#编写server端)
    * [实现接口](https://www.selinux.tech/golang/grpc/pages/-LgMx9-UagQUaDRJoQv1#实现接口)
    * [启动server端](https://www.selinux.tech/golang/grpc/pages/-LgMx9-UagQUaDRJoQv1#启动server端)
  * [编写client端](https://www.selinux.tech/golang/grpc/pages/-LgMx9-UagQUaDRJoQv1#编写client端)
    * [创建一个存根/客户端](https://www.selinux.tech/golang/grpc/pages/-LgMx9-UagQUaDRJoQv1#创建一个存根客户端)
    * [调用服务端方法](https://www.selinux.tech/golang/grpc/pages/-LgMx9-UagQUaDRJoQv1#调用服务端方法)
    * [Run it](/golang/grpc/grpc-basic#run-it)

接下来我们就开始以go为基础去使用gRPC。本文将介绍：

* 在 `.proto` 文件中进行服务定义。
* 使用 `protocol buffer` 编译器生成 server 端 和client 端代码。
* 使用 Go gRPC API 为我们定义的服务编写简单的 client和server。

## Example Code

我们这里使用的示例是 [grpc/grpc-go/examples/route\_guide](https://github.com/grpc/grpc-go/tree/master/examples/route_guide), 为了使用这个示例，可以先下载 `grpc-go` 这个库。

```
go get google.golang.org/grpc
```

然后切换到 `grpc/grpc-go/examples/route_guide`目录下。

```
cd $GOPATH/src/google.golang.org/grpc/examples/route_guide
```

## 定义服务

从前面的介绍中我们已经介绍如何使用 `protocol buffers`来定义服务以及返回值类型。

```
// Interface exported by the server.
service RouteGuide {
  // 一元RPC
  // A simple RPC.
  //
  // Obtains the feature at a given position.
  //
  // A feature with an empty name is returned if there's no feature at the given
  // position.
  rpc GetFeature(Point) returns (Feature) {}

  // 服务端流式RPC
  // A server-to-client streaming RPC.
  //
  // Obtains the Features available within the given Rectangle.  Results are
  // streamed rather than returned at once (e.g. in a response message with a
  // repeated field), as the rectangle may cover a large area and contain a
  // huge number of features.
  rpc ListFeatures(Rectangle) returns (stream Feature) {}

  // 客户端流式RPC
  // A client-to-server streaming RPC.
  //
  // Accepts a stream of Points on a route being traversed, returning a
  // RouteSummary when traversal is completed.
  rpc RecordRoute(stream Point) returns (RouteSummary) {}

  // 双向流式RPC
  // A Bidirectional streaming RPC.
  //
  // Accepts a stream of RouteNotes sent while a route is being traversed,
  // while receiving other RouteNotes (e.g. from other users).
  rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
}
```

同时在 `.proto` 文件中还定义了用于请求和响应的message 类型。

```
// Points are represented as latitude-longitude pairs in the E7 representation
// (degrees multiplied by 10**7 and rounded to the nearest integer).
// Latitudes should be in the range +/- 90 degrees and longitude should be in
// the range +/- 180 degrees (inclusive).
message Point {
  int32 latitude = 1;
  int32 longitude = 2;
}

// A latitude-longitude rectangle, represented as two diagonally opposite
// points "lo" and "hi".
message Rectangle {
  // One corner of the rectangle.
  Point lo = 1;

  // The other corner of the rectangle.
  Point hi = 2;
}

// A feature names something at a given point.
//
// If a feature could not be named, the name is empty.
message Feature {
  // The name of the feature.
  string name = 1;

  // The point where the feature is detected.
  Point location = 2;
}

// A RouteNote is a message sent while at a given point.
message RouteNote {
  // The location from which the message is sent.
  Point location = 1;

  // The message to be sent.
  string message = 2;
}

// A RouteSummary is received in response to a RecordRoute rpc.
//
// It contains the number of individual points received, the number of
// detected features, and the total distance covered as the cumulative sum of
// the distance between each point.
message RouteSummary {
  // The number of points received.
  int32 point_count = 1;

  // The number of known features passed while traversing the route.
  int32 feature_count = 2;

  // The distance covered in metres.
  int32 distance = 3;

  // The duration of the traversal in seconds.
  int32 elapsed_time = 4;
}
```

## 生成server端和client 端代码

对 `route_guide`目录执行以下的命令。将会在该目录下 生成一个route\_guide.pb.go 的go 文件。

```
protoc -I routeguide/ routeguide/route_guide.proto --go_out=plugins=grpc:routeguide
```

这里面将包含：

* 所有的 `protocol buffers`代码用于填充，序列化和检索我们的请求和响应消息类型。
* 客户端使用RouteGuide服务中定义的方法时，调用的接口类型。
* 服务端要实现的接口类型，以及RouteGuide服务中定义的方法

## 编写Server端

gRPC已经为我们自动生成了编写Server端需要实现的接口和方法。接下来我们就来实现一个gRPC Server。一共有两个过程：

* 实现服务定义中的接口
* 运行gRPC服务,以侦听来自客户端的请求并将其分派给正确的服务实现。

具体示例可以在 [grpc-go/examples/route\_guide/server/server.go](https://github.com/grpc/grpc-go/blob/master/examples/route_guide/server/server.go)中查看。接下来我们分析下，具体都干了些什么？

### 实现接口

可以看到有一个 `routeGuideServer`的结构体，实现了 `RouteGuideServer` 接口

```go
type routeGuideServer struct {
        ...
}
...

func (s *routeGuideServer) GetFeature(ctx context.Context, point *pb.Point) (*pb.Feature, error) {
        ...
}
...

func (s *routeGuideServer) ListFeatures(rect *pb.Rectangle, stream pb.RouteGuide_ListFeaturesServer) error {
        ...
}
...

func (s *routeGuideServer) RecordRoute(stream pb.RouteGuide_RecordRouteServer) error {
        ...
}
...

func (s *routeGuideServer) RouteChat(stream pb.RouteGuide_RouteChatServer) error {
        ...
}
...
```

`routeGuideServer` 实现了所有的服务接口.

**Simple RPC**

我们先以最简单的(Simple RPC)为例，来看看他是具体是怎么实现的。他是从客户端获取一个point，并返回一个Feature。

```go
// GetFeature returns the feature at the given point.
func (s *routeGuideServer) GetFeature(ctx context.Context, point *pb.Point) (*pb.Feature, error) {
    for _, feature := range s.savedFeatures {
        if proto.Equal(feature.Location, point) {
            return feature, nil
        }
    }
    // No feature was found, return an unnamed feature
    return &pb.Feature{Location: point}, nil
}
```

该方法传递了 RPC 上下文对象和客户端的 `Point` `protocol buffers` 请求。它返回一个Feature协议缓冲区对象，其中包含响应信息和 error。我们使用适当的信息填充Feature，然后将其与nil错误一起返回，告诉gRPC我们已经完成了RPC的处理，并且Feature可以返回给客户端。

**Server-side streaming RPC**

接下来看下服务端流式RPC。ListFeatures是服务器端流式RPC，因为我们需要发送多个 Features 给客户端。

```go
// ListFeatures lists all features contained within the given bounding Rectangle.
func (s *routeGuideServer) ListFeatures(rect *pb.Rectangle, stream pb.RouteGuide_ListFeaturesServer) error {
    for _, feature := range s.savedFeatures {
        if inRange(feature.Location, rect) {
            if err := stream.Send(feature); err != nil {
                return err
            }
        }
    }
    return nil
}
```

如你所见，这次与简单请求的request和reponse不同，这里获取了两个比较复杂的参数`pb.Rectangle`, `RouteGuide_ListFeaturesServer`来进行处理并构建response。在这个方法里，我们构建了多个 Feature 对象，并使用send方法将他们写入到`RouteGuide_ListFeaturesServer`中。返回了一个nil的错误。gRPC层会将其转换为适当的RPC状态，以便在链路中发送。

**Client-side streaming RPC**

接下来来看一个比较复杂的。客户端流式gRPC.服务端从客户端获取一系列的`Points`返回单个`RouteSummary`.

```go
// RecordRoute records a route composited of a sequence of points.
//
// It gets a stream of points, and responds with statistics about the "trip":
// number of points,  number of known features visited, total distance traveled, and
// total time spent.
func (s *routeGuideServer) RecordRoute(stream pb.RouteGuide_RecordRouteServer) error {
    var pointCount, featureCount, distance int32
    var lastPoint *pb.Point
    startTime := time.Now()
    for {
        point, err := stream.Recv()
        if err == io.EOF {
            endTime := time.Now()
            return stream.SendAndClose(&pb.RouteSummary{
                PointCount:   pointCount,
                FeatureCount: featureCount,
                Distance:     distance,
                ElapsedTime:  int32(endTime.Sub(startTime).Seconds()),
            })
        }
        if err != nil {
            return err
        }
        pointCount++
        for _, feature := range s.savedFeatures {
            if proto.Equal(feature.Location, point) {
                featureCount++
            }
        }
        if lastPoint != nil {
            distance += calcDistance(lastPoint, point)
        }
        lastPoint = point
    }
}
```

正如我们所看到的，这次没有接收任何的request 请求参数，而是直接接收了一个 `RouteGuide_RecordRouteServer`流。服务端通过这个流**读写**消息。读的话，使用`Recv()`方法，返回单个response 使用它的 `SendAndClose()` 方法。

在这个方法体中，我们使用 `RouteGuide_RecordRouteServer`的`Recv()` 方法，重复读取客户端的请求到某一个实体中（本例中是point）,直到读取不出任何消息为止。 服务端需要去检查每一次Read返回的错误。如果error为nil，说明当前流没有问题，可以继续读取。如果是`io.EOF`，说明当前读取已经结束，server端可以返回 `RouteSummary`,如果它有其他类型的值，则将其原样返回。RPC会自动将其转换成相应的RPC 状态。

**Bidirectional streaming RPC**

废话不多说，上来先看下代码

```go
// RouteChat receives a stream of message/location pairs, and responds with a stream of all
// previous messages at each of those locations.
func (s *routeGuideServer) RouteChat(stream pb.RouteGuide_RouteChatServer) error {
    for {
        in, err := stream.Recv()
        if err == io.EOF {
            return nil
        }
        if err != nil {
            return err
        }
        key := serialize(in.Location)

        s.mu.Lock()
        s.routeNotes[key] = append(s.routeNotes[key], in)
        // Note: this copy prevents blocking other clients while serving this one.
        // We don't need to do a deep copy, because elements in the slice are
        // insert-only and never modified.
        rn := make([]*pb.RouteNote, len(s.routeNotes[key]))
        copy(rn, s.routeNotes[key])
        s.mu.Unlock()

        for _, note := range rn {
            if err := stream.Send(note); err != nil {
                return err
            }
        }
    }
}
```

这次我们又接收到了`RouteGuide_RouteChatServer`流，与客户端流式RPC一样，仍然可以使用它来读写数据。但是，这次我们可以在客户端还在向stream中写数据的同时就返回值。

从代码中可以看出，server端这里读写数据的方法非常类似于客户端流式RPC里面的方法，无非就是这里使用了 `send()`方法，而不是`SendAndClose()`方法。因为send可以发送多个reponse。尽管每一端都能够按照对方发送消息的顺序读取其相应的消息，但是客户端和服务端都可以按照任何顺序进行读写，这两个流完全独立运行。

### 启动server端

一旦实现了所有的方法，我们就需要开启一个gRPC server，让客户端能够访问我们的服务。

```go
flag.Parse()
    lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *port))
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    // 是否使用TLS
    var opts []grpc.ServerOption
    if *tls {
        if *certFile == "" {
            *certFile = testdata.Path("server1.pem")
        }
        if *keyFile == "" {
            *keyFile = testdata.Path("server1.key")
        }
        creds, err := credentials.NewServerTLSFromFile(*certFile, *keyFile)
        if err != nil {
            log.Fatalf("Failed to generate credentials %v", err)
        }
        opts = []grpc.ServerOption{grpc.Creds(creds)}
    }
    grpcServer := grpc.NewServer(opts...)
    pb.RegisterRouteGuideServer(grpcServer, newServer())
    grpcServer.Serve(lis)
```

* 指定端口
* 启动 gRPC server实例
* 将我们实现的service注册到 the gRPC server.
* `Serve()`方法启动，`Stop()`方法停止

## 编写client端

接下来就开始编写一个 gRPC client 来访问 `RouteGuide` 服务。 示例 代码 [grpc-go/examples/route\_guide/client/client.go](https://github.com/grpc/grpc-go/blob/master/examples/route_guide/client/client.go)

### 创建一个存根/客户端

为了调用服务端发方法，需要创建一个 gRPC channel 来跟server端进行通信。使用 `grpc.Dial()`方法，传入server端的地址和端口来实现。

```go
conn, err := grpc.Dial(*serverAddr)
if err != nil {
    ...
}
defer conn.Close()
```

还可以使用 `DialOptions` 在 `grpc.Dial` 设置使用证书认证等，例如下面这样

```go
flag.Parse()
    var opts []grpc.DialOption
    if *tls {
        if *caFile == "" {
            *caFile = testdata.Path("ca.pem")
        }
        creds, err := credentials.NewClientTLSFromFile(*caFile, *serverHostOverride)
        if err != nil {
            log.Fatalf("Failed to create TLS credentials %v", err)
        }
        opts = append(opts, grpc.WithTransportCredentials(creds))
    } else {
        opts = append(opts, grpc.WithInsecure())
    }
    conn, err := grpc.Dial(*serverAddr, opts...)
    if err != nil {
        log.Fatalf("fail to dial: %v", err)
    }
    defer conn.Close()
```

gRPC channel 建立好之后，需要一个client stub来 执行RPC调用。我们使用 `.proto` 文件中生成的 `pb` 包中的 `NewRouteGuideClient` 方法来创建。

```go
client := pb.NewRouteGuideClient(conn)
```

### 调用服务端方法

接下来我们看客户端如何调用服务端方法。**请注意,在gRPC-Go中，RPC以阻塞/同步模式运行，这意味着RPC调用等待服务器响应，并将返回响应或错误。**

**Simple RPC**

```go
  feature, err := client.GetFeature(context.Background(), &pb.Point{409146138, -746188906})
  if err != nil {
        ...
  }
```

就像调用本地方法一样简单，在方法中我们构造了一个 `protocol buffers` 对象 `db.Point`,我们还传递了一个context.Context对象，它允许我们在必要时更改RPC的行为，例如rpc调用还在进行时取消RPC. 如果没有出现错误，我们就能收到 server端的返回值。

看一下完整的写法。

```go
// printFeature gets the feature for the given point.
func printFeature(client pb.RouteGuideClient, point *pb.Point) {
   log.Printf("Getting feature for point (%d, %d)", point.Latitude, point.Longitude)
   ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   defer cancel()
   feature, err := client.GetFeature(ctx, point)
   if err != nil {
      log.Fatalf("%v.GetFeatures(_) = _, %v: ", client, err)
   }
   log.Println(feature)
}
```

**Server-side streaming RPC**

接下来调用服务端流式RPC方法 `ListFeatures`，这个方法返回一系列的地理数据 `Features`.

```go
rect := &pb.Rectangle{ ... }  // initialize a pb.Rectangle
stream, err := client.ListFeatures(context.Background(), rect)
if err != nil {
    ...
}
for {
    feature, err := stream.Recv()
    if err == io.EOF {
        break
    }
    if err != nil {
        log.Fatalf("%v.ListFeatures(_) = _, %v", client, err)
    }
    log.Println(feature)
}
```

与简单的RPC调用一样，传入了context.Context来进行request，但是与其不一样的是，这次没有简单收到了一个repsonse对象，而是接收到了一个`RouteGuide_ListFeaturesClient`实例。客户端会使用 `RouteGuide_ListFeaturesClient` 来读取server端的消息。

我们使用 `RouteGuide_ListFeaturesClient`的`Recv（）`方法重复读取服务端对 `protocol buffers`对象（在本例中为Feature）的响应，直到没有更多消息为止。客户端需要对每一个调用中返回的error进行判断，错误的处理方式与server端类似。

看下完整的写法。

```go
// printFeatures lists all the features within the given bounding Rectangle.
func printFeatures(client pb.RouteGuideClient, rect *pb.Rectangle) {
   log.Printf("Looking for features within %v", rect)
   ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   defer cancel()
   stream, err := client.ListFeatures(ctx, rect)
   if err != nil {
      log.Fatalf("%v.ListFeatures(_) = _, %v", client, err)
   }
   for {
      feature, err := stream.Recv()
      if err == io.EOF {
         break
      }
      if err != nil {
         log.Fatalf("%v.ListFeatures(_) = _, %v", client, err)
      }
      log.Println(feature)
   }
}
```

**Client-side streaming RPC**

客户端流方法RPC 方法 RecordRoute类似于服务器端方法，除了我们只传递方法一个上下文并获取一个可以读写消息的RouteGuide\_RecordRouteClient流。

```go
// runRecordRoute sends a sequence of points to server and expects to get a RouteSummary from server.
func runRecordRoute(client pb.RouteGuideClient) {
    // Create a random number of random points
    r := rand.New(rand.NewSource(time.Now().UnixNano()))
    pointCount := int(r.Int31n(100)) + 2 // Traverse at least two points
    var points []*pb.Point
    for i := 0; i < pointCount; i++ {
        points = append(points, randomPoint(r))
    }
    log.Printf("Traversing %d points.", len(points))
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    stream, err := client.RecordRoute(ctx)
    if err != nil {
        log.Fatalf("%v.RecordRoute(_) = _, %v", client, err)
    }
    for _, point := range points {
        if err := stream.Send(point); err != nil {
            log.Fatalf("%v.Send(%v) = %v", stream, point, err)
        }
    }
    reply, err := stream.CloseAndRecv()
    if err != nil {
        log.Fatalf("%v.CloseAndRecv() got error %v, want %v", stream, err, nil)
    }
    log.Printf("Route summary: %v", reply)
}
```

`RouteGuide_RecordRouteClient` 流有一个Send()方法，我们可以用它向server端发送request请求。一旦我们使用 `Send()`方法发送完了请求，我们就需要调用 `CloseAndRecv()`关闭这个流，以便gRPC知道我们已经完成了写操作并希望收到response。`CloseAndRecv()` 方法会返回RPC结果的状态。如果err为nil，那么第一个结果 reply 就是这次请求正确的结果返回值。

**Bidirectional streaming RPC**

最后，我们来看下双向流式RPC调用 `RouteChat（）`。 与`RecordRoute`的情况一样，我们只传递方法一个上下文对象并返回一个我们可用于写入和读取消息的流。 但是，我们能够我们能够在向这个流写入数据的同时就通过这个流获取到一些返回值。

下面看下完整的代码实现

```go
// runRouteChat receives a sequence of route notes, while sending notes for various locations.
func runRouteChat(client pb.RouteGuideClient) {
    notes := []*pb.RouteNote{
        {Location: &pb.Point{Latitude: 0, Longitude: 1}, Message: "First message"},
        {Location: &pb.Point{Latitude: 0, Longitude: 2}, Message: "Second message"},
        {Location: &pb.Point{Latitude: 0, Longitude: 3}, Message: "Third message"},
        {Location: &pb.Point{Latitude: 0, Longitude: 1}, Message: "Fourth message"},
        {Location: &pb.Point{Latitude: 0, Longitude: 2}, Message: "Fifth message"},
        {Location: &pb.Point{Latitude: 0, Longitude: 3}, Message: "Sixth message"},
    }
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    stream, err := client.RouteChat(ctx)
    if err != nil {
        log.Fatalf("%v.RouteChat(_) = _, %v", client, err)
    }
    waitc := make(chan struct{})
    go func() {
        for {
            in, err := stream.Recv()
            if err == io.EOF {
                // read done.
                close(waitc)
                return
            }
            if err != nil {
                log.Fatalf("Failed to receive a note : %v", err)
            }
            log.Printf("Got message %s at point(%d, %d)", in.Message, in.Location.Latitude, in.Location.Longitude)
        }
    }()
    for _, note := range notes {
        if err := stream.Send(note); err != nil {
            log.Fatalf("Failed to send a note: %v", err)
        }
    }
    stream.CloseSend()
    <-waitc
}
```

读写消息的方式 与 客户端流式RPC 调用很类似，无非就是需要使用 `CloseSend()`方法来关闭流。尽管客户端和服务端都会按照对方写入的消息顺序来读取，但是他们可以以任意的顺序写入,这个流是完全独立运行的。

### Run it

接下来运行以下 server端和client 端试一下。

```
go run server/server.go
```

```
go run client/client.go
```


# 5、gRPC组件ProtocolBuffers介绍

* [ProtocolBuffers](/golang/grpc/protocol-buffers#protocolbuffers)
  * [What are protocol buffers?](/golang/grpc/protocol-buffers#what-are-protocol-buffers)
  * [How do they work?](/golang/grpc/protocol-buffers#how-do-they-work)
  * [proto3](/golang/grpc/protocol-buffers#proto3)
    * [定义消息类型](https://www.selinux.tech/golang/grpc/pages/-LhJ-YQ_RPay_W780-03#定义消息类型)
    * [分配字段编号](https://www.selinux.tech/golang/grpc/pages/-LhJ-YQ_RPay_W780-03#分配字段编号)
    * [指定字段规则](https://www.selinux.tech/golang/grpc/pages/-LhJ-YQ_RPay_W780-03#指定字段规则)
    * [添加更多消息类型](https://www.selinux.tech/golang/grpc/pages/-LhJ-YQ_RPay_W780-03#添加更多消息类型)
    * [添加注释](https://www.selinux.tech/golang/grpc/pages/-LhJ-YQ_RPay_W780-03#添加注释)
    * [保留字段](https://www.selinux.tech/golang/grpc/pages/-LhJ-YQ_RPay_W780-03#保留字段)
    * [Scalar值类型](https://www.selinux.tech/golang/grpc/pages/-LhJ-YQ_RPay_W780-03#scalar值类型)
    * [默认值](https://www.selinux.tech/golang/grpc/pages/-LhJ-YQ_RPay_W780-03#默认值)
    * [枚举](https://www.selinux.tech/golang/grpc/pages/-LhJ-YQ_RPay_W780-03#枚举)
    * [import proto](/golang/grpc/protocol-buffers#import-proto)
    * [内嵌类型](https://www.selinux.tech/golang/grpc/pages/-LhJ-YQ_RPay_W780-03#内嵌类型)
    * [更新消息类型](https://www.selinux.tech/golang/grpc/pages/-LhJ-YQ_RPay_W780-03#更新消息类型)
    * [Any](/golang/grpc/protocol-buffers#any)
    * [Oneof](/golang/grpc/protocol-buffers#oneof)
      * [Oneof 特性](https://www.selinux.tech/golang/grpc/pages/-LhJ-YQ_RPay_W780-03#oneof-特性)
      * [标签重用问题](https://www.selinux.tech/golang/grpc/pages/-LhJ-YQ_RPay_W780-03#标签重用问题)
    * [Maps](/golang/grpc/protocol-buffers#maps)
    * [定义服务](https://www.selinux.tech/golang/grpc/pages/-LhJ-YQ_RPay_W780-03#定义服务)
    * [JSON 映射](https://www.selinux.tech/golang/grpc/pages/-LhJ-YQ_RPay_W780-03#json-映射)
  * [参考](https://www.selinux.tech/golang/grpc/pages/-LhJ-YQ_RPay_W780-03#参考)

这篇文章我们来介绍一下 Protocol Buffers , 官方地址 <https://developers.google.com/protocol-buffers/docs/overview>

## What are protocol buffers?

`Protocol Buffers` 是一种灵活，高效、自动化的机制用来序列化结构化数据。有些类似于 json,xml，但是更简单，更小，更高效。我们可以再一个文件中定义好结构化数据，然后使用工具生成代码，和读取各种数据流。

## How do they work?

前面的文章中，我们介绍过，就是在 `.proto` 文件中定义消息类型，并指定这些消息的内部字段属性，就可以了。

例如下面这个基于golang的 proto 定义

```
message Person {
  required string name = 1;
  required int32 id = 2;
  optional string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }

  repeated PhoneNumber phone = 4;
}
```

在上面的示例中，`Person`消息包含 `PhoneNumber`消息，而`AddressBook`消息包含`Person`消息。我们甚至可以定义嵌套在其他消息中的消息类型 ，例如`PhoneNumber`类型定义在`Person`内部，而且还可以定义enum，用来指定PhoneNum是哪里的，HOMW,WORK ...

使用了上面的定义之后，我们就可以使用protoc编译器，来生成相应语言的代码了，同时还会一并生成相应的Get和Set方法，以及marshal和unmarshal结构化数据的方法。参考我们前面的例子。

## proto3

接下来，我们来介绍一下 proto3(新版本的ProtocolBuffers),以及`.proto`文件的语法。

### 定义消息类型

先来看一个简单的例子。假设我们需要定义一个查询请求，这个message 包含一个查询语句 `query`，包含的页数 `page_number`,每页包含的结果数`result_per_page`.那么我么的 `proto` 文件可以像下面这样定义。

```
syntax = "proto3";

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}
```

* `syntax = "proto3"` 必须位于首行,第一个非注释行，如果不指定，默认使用`proto2`
* `SearchRequest`指定了三个字段(name/value 对),每个field都有自己的type。

具体的field type 可以参考 [Scalar Value Types](https://developers.google.com/protocol-buffers/docs/proto3#scalar)

### 分配字段编号

从上面的定义中可以看出，每个字段都有一个唯一编号，这些数字，用于在将message二进制序列化时标识字段。这里有一点需要注意，1\~15范围需要一个byte来进行编码，包括字段编号和字段类型，可以从[Protocol Buffer Encoding](https://developers.google.com/protocol-buffers/docs/encoding#structure)查到相关内容。 16\~2047 占用两个字节。所以一般会把 1\~15留给哪些频繁出现的字段。或者是预留一些空间给将来可能频繁出现的元素。

字段编号最小可以指定为1，最大 2^29-1 。但是 19000\~19999 这1000个数不要使用，这是protocol保留的。

### 指定字段规则

字段规则一般就下面两种：

* `singular` : 可以包含零个或一个，默认的
* `repeated` : 可以重复多次，包括零次。同时，其重复的顺序也会被保留。

在 `proto3` 中，简单数字类型的 `repeated` 字段默认使用 `packed` 编码。

详细内容可以参考 [Protocol Buffer Encoding](https://developers.google.com/protocol-buffers/docs/encoding#packed)

### 添加更多消息类型

简单 略过

### 添加注释

简单 略过

### 保留字段

在更新message type时，假设需要彻底删除一个field，或者注释掉，这样未来其他人就可以继续使用之前分配给这个字段的编号。如果他们以后加载了相同 `.proto` 文件的旧版本，这可能会导致严重问题，包括数据损坏，隐私错误等。有一个办法就是将要删除的字段设置为保留字段，未来任何用户试图使用这个字段的时候，protocol buffer 的编译器就会告警提示。

```
message Foo {
  reserved 2, 15, 9 to 11;
  reserved "foo", "bar";
}
```

但是要注意不能在同一个保留字段声明中混合使用数字和 Field name.

### Scalar值类型

这个可以参考官方网站 [Scalar Value Types](https://developers.google.com/protocol-buffers/docs/proto3#scalar)

这里主要把java和golang的值列举一下，其他的可以参考官方网站。

| .proto type | java type  | golang type |
| ----------- | ---------- | ----------- |
| double      | double     | float64     |
| float       | float      | float32     |
| int32       | int        | int32       |
| int64       | long       | int64       |
| uint32      | int        | uint32      |
| uint64      | long       | uint64      |
| sint32      | int        | int32       |
| sint64      | long       | int64       |
| fixed32     | int        | uint32      |
| fixed64     | long       | uint64      |
| sfixed32    | int        | int32       |
| sfixed64    | long       | int64       |
| bool        | boolean    | bool        |
| string      | String     | string      |
| bytes       | ByteString | \[]byte     |

### 默认值

* 对于strings, 默认值是空字符串(注, 是"", 而不是null)
* 对于bytes, 默认值是空字节(注, 应该是byte\[0], 注意这里也不是null)
* 对于boolean, 默认值是false.
* 对于数字类型, 默认值是0.
* 对于枚举, 默认值是第一个定义的枚举值, 而这个值必须是0.
* 对于消息字段, 默认值是null.

对于重复字段, 默认值是空(通常都是空列表)

注意: 对于简单字段, 当消息被解析后, 如果值恰巧和默认值相同(例如一个boolean设置为false)是没有办法知道这个字段到底是有设置值还是取了默认值。这样就要求，不要根据默认值来采取某些切换行为，例如当某个 boolean 值为false时，切换状态。同样的，如果一个字段被设置了默认值，这个值不会被序列化。

### 枚举

当定义消息类型时, 我们希望某个字段只能有预先定义的多个值中的一个. 例如, 为每个SearchRequest添加一个corpus字段, 而corpus可以是UNIVERSAL, WEB, IMAGES, LOCAL, NEWS, PRODUCTS 或 VIDEO . 这样就可以简单的添加一个枚举到消息定义, 为每个可能的值定义常量.

```
message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
  enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
  }
  Corpus corpus = 4;
}
```

举的第一个常量设置到0: 每个枚举定义必须包含一个映射到0的常量作为它的第一个元素. 这是因为:

* 必须有一个0值, 这样我们才能用0来作为数值默认值
* 0值必须是第一个元素, 兼容proto2语法,在proto2中默认值总是第一个枚举值

可以通过将相同值赋值给不同的枚举常量来定义别名. 为此需要设置allow\_alias选项为true,否则protocol编译器会报错。

```
enum EnumAllowingAlias {
  option allow_alias = true;
  UNKNOWN = 0;
  STARTED = 1;
  RUNNING = 1;
}
enum EnumNotAllowingAlias {
  UNKNOWN = 0;
  STARTED = 1;
  // RUNNING = 1;  // Uncommenting this line will cause a compile error inside Google and a warning message outside.
}
```

枚举常量必须在32位整形的范围内. 由于枚举值使用 `varint encoding`, 负值是效率低下的因此不推荐使用.

### import proto

像编写代码一样,proto文件也支持从其他的proto文件中导入message 定义。

> import "myproject/other\_protos.proto";

**但是这里要注意**

假设 `a.proto`引入了`b.proto`，但是`b.proto`更换了位置，路径变成了`test/b.proto`,那有下面的办法可以解决:

* 修改`a.proto`中的import语句，直接`import "test/b.proto"`
* 在`b.proto`文件原来的位置，创建一个`b.proto`文件，文件内容为`import public "test/b.proto"`，就可以了

假设 `a.proto`引入了`b.proto`，`b.proto` 中引用 `c.proto`,如果 `a.proto`想要引用`c.proto`，是不能直接用的，同样有下面两种办法：

* 在`a.proto`中新增`c.proto`的引用
* 在`b.proto`中将引用修改为 \`import public "c.proto"\`\`

### 内嵌类型

与其他编程语言一样，Protocol Buffer 支持内嵌类型，并且可以内嵌多层。

```
message SearchResponse {
  message Result {
    string url = 1;
    string title = 2;
    repeated string snippets = 3;
  }
  repeated Result result = 1;
}
```

如果想在父消息类型之外重用消息类型, 可以使用 Parent.Type 来引用:

```
message SomeOtherMessage {
  SearchResponse.Result result = 1;
}
```

还可以嵌的更深

```
message Outer {                  // Level 0
  message MiddleAA {  // Level 1
    message Inner {   // Level 2
      int64 ival = 1;
      bool  booly = 2;
    }
  }
  message MiddleBB {  // Level 1
    message Inner {   // Level 2
      int32 ival = 1;
      bool  booly = 2;
    }
  }
}
```

### 更新消息类型

如果现有的消息类型不再满足所有需求 (例如，添加额外的字段 )，但仍然希望使用使用旧格式创建的代码，不用担心，在不破坏任何现有代码的情况下更新消息类型非常简单。请记住以下规则：

* **不要**更改任何现有字段的字段编号；
* 如果添加新的字段时, 使用"老"消息格式序列化后的任何消息都可以被新生成的代码解析. 但是需要留意这些元素的默认值以便新的代码可以正确和老代码生成的消息交互(也就是说，新添加的字段此时会采用默认值，因为lao的消息传递过来的时候不会包含这些字段). 类似的, 新代码创建的消息可以被老代码解析: 解析时新的字段被简单的忽略. 当消息反序列化时未知字段会失效, 因此如果消息被传递给新代码, 新的字段将不再存在.
* 字段可以被删除, 但是要求在更新后的消息类型中原来的标签数字不再使用.可以考虑重命名这个字段, 或者添加前缀`OBSOLETE_`, 或者`reserved`, 以便其他用户在修改.proto文件不会不小心重用这个数字.
* `int32`, `uint32`, `int64`, `uint64`, 和 `bool` 是完全兼容的 ,也就是说可以将一个字段的类型从这些类型中的一个修改为另外一个，而不会打破向前或者向后兼容. 也就是说，类型解析时会做一下相应的类型转换。
* sint32 和 sint64 是彼此兼容的,但是和其他整型类型不兼容.
* string 和 bytes 是兼容的, 如果bytes是有效的UTF-8编码的.
* 如果bytes包含这个消息编码后的内容，内嵌的message type 和bytes兼容,.
* fixed32 兼容 sfixed32, 而 fixed64 兼容 sfixed64.

### Any

Any 消息类型可以让你使用消息作为嵌入类型而不必持有他们的.proto定义. Any把任意序列化后的消息作为bytes包含, 带有一个URL, 工作起来类似一个全局唯一的标识符. 为了使用Any类型, 需要导入google/protobuf/any.proto.

```
import "google/protobuf/any.proto";

message ErrorStatus {
  string message = 1;
  repeated Any details = 2;
}
```

给定消息类型的 default type URL 是 `type.googleapis.com/packagename.messagename`.

不同语言实现将提供运行时类库来帮助以类型安全的方式封包和解包Any的内容,例如, 在java中, Any类型将有特别的pack()和unpark()访问器, 而在c++中有PackFrom() 和 PackTo()方法:

```cpp
// 在Any中存储任务消息类型
NetworkErrorDetails details = ...;
ErrorStatus status;
status.add_details()->PackFrom(details);

// 从Any中读取任意消息
ErrorStatus status = ...;
for (const Any& detail : status.details()) {
  if (detail.IsType<NetworkErrorDetails>()) {
    NetworkErrorDetails network_error;
    detail.UnpackTo(&network_error);
    ... processing network_error ...
  }
}
```

### Oneof

如果你有一个有很多字段的消息, 而同一时间最多只有一个字段会被设值, 你可以通过使用`oneof`特性来强化这个行为并节约内存.

Oneof 字段和常见字段类似, 除了所有字段共用内存, 并且同一时间最多有一个字段可以设值. 设值`oneof`的任何成员都将自动清除所有其他成员. 可以通过使用特殊的case()或者WhichOneof()方法来检查oneof中的哪个值被设值了(如果有), 取决于不同语言.

使用oneof关键字来在.proto中定义oneof, 后面跟oneof名字, 在这个例子中是test\_oneof:

```
message SampleMessage {
  oneof test_oneof {
    string name = 4;
    SubMessage sub_message = 9;
  }
}
```

然后再将oneof字段添加到oneof定义. 可以添加任意类型的字段, 但是不能使用重复(repeated)字段.

在生成的代码中, oneof字段和普通字段一样有同样的getter和setter方法. 也会有一个特别的方法用来检查哪个值(如果有)被设置了,可以点击 [API Reference](https://developers.google.com/protocol-buffers/docs/reference/overview) 查看更多。

#### Oneof 特性

* 设置一个oneof字段会自动清除所有其他oneof成员. 所以如果设置多次oneof字段, 只有最后设置的字段依然有值.

```cpp
SampleMessage message;
message.set_name("name");
CHECK(message.has_name());
message.mutable_sub_message();   // Will clear name field.
CHECK(!message.has_name());
```

* 如果解析器遇到同一个oneof的多个成员, 只有看到的最后一个成员在被解析的消息中被使用.
* oneof不能是重复字段
* Reflection APIs work for oneof fields. (oneof字段用反射api来实现?)
* 如果使用c++, 需要兼顾确认确认代码不会导致内存奔溃. 下面的示例代码会导致crash因为sub\_message已经在调用set\_name()方法时被删除.

```cpp
SampleMessage message;
SubMessage* sub_message = message.mutable_sub_message();
message.set_name("name");      // Will delete sub_message
sub_message->set_...            // Crashes here
```

* 同样在c++中, 如果通过调用Swap()来交换两个带有oneof的消息, 每个消息将会有另外一个消息的oneof: 在下面这个示例中, msg1将会有sub\_message和msg2会有name.

```cpp
SampleMessage msg1;
msg1.set_name("name");
SampleMessage msg2;
msg2.mutable_sub_message();
msg1.swap(&msg2);
CHECK(msg1.has_sub_message());
CHECK(msg2.has_name());
```

#### 标签重用问题

* 将字段移入或者移出oneof: 消息被序列化和解析后, 可能丢失部分信息(某些字段可能被清除). 但是，可以安全地将单个字段移动到新的AAA中，并且如果已知只有一个字段被设置，还可以移动多个字段。
* 删除oneof的一个字段又回来: 消息被序列化和解析后, 可能清除你当前设置的oneof字段
* 拆分或者合并oneof: 和移动普通字段一样有类似问题

### Maps

如果想创建一个map作为数据定义的一部分, 可以使用下面的语法:

> map map\_field = N;

key\_type可以是任意整型或者字符类型( 除了floating point和bytes外任何简单类型). value\_type可以是任意类型.

这与大多数编程语言类似,例如下面的代码，projects 是另一个 message type。

```
map<string, Project> projects = 3;
```

**map语法和下面的代码等同**

```
message MapFieldEntry {
  key_type key = 1;
  value_type value = 2;
}

repeated MapFieldEntry map_field = N;
```

### 定义服务

前面的文章中已经介绍过多次，这里不再重复介绍。

```
service SearchService {
  rpc Search (SearchRequest) returns (SearchResponse);
}
```

### JSON 映射

Proto3支持JSON格式的标准编码, 让不同系统之间的通信变的兼容。

如果一个值在json编码的数据中丢失或者它的值是null, 在被解析成protocol buffer时它将设置为对应的默认值.如果一个字段的值正好是protocol buffer的默认值, 这个字段默认就不会出现在json编码的数据中以便节约空间.

点击 [JSON Mapping](https://developers.google.com/protocol-buffers/docs/proto3#json) 查看proto3 与JSON 的编码对应。

**关于proto3的介绍暂时先写这么多，实际应用中可以再去官方网站查看相应的详细内容**。

## 参考

* [Protocol Buffer 官网](https://developers.google.com/protocol-buffers/)
* [Protocol Buffer 3 学习笔记](https://skyao.io/learning-proto3/)


# 6、gRPC组件Http 2.0

* [HTTP 2.0](/golang/grpc/http2#HTTP-20)
  * [尝鲜一下](https://www.selinux.tech/golang/grpc/pages/-LhJ-YQaMc8FzG5wi0Ap#尝鲜一下)
  * [HTTP 1.1 VS HTTP 2.0](/golang/grpc/http2#HTTP-11-VS-HTTP-20)
  * [gRPC基于HTTP/2的优缺点](https://www.selinux.tech/golang/grpc/pages/-LhJ-YQaMc8FzG5wi0Ap#gRPC基于HTTP2的优缺点)

关于 HTTP 2.0 具体有哪些新的内容，标准发生了什么变化，或许不是一篇文章就能够说明白的。或许，我们自己瞎解释一通，不如将一些有用的资料列举出来更有意义。

## 尝鲜一下

首先，我们来直观的体验一下HTTP2.0 。 [HTTP/2 is the future of the Web, and it is here!](https://http2.akamai.com/demo) Akamai 公司 建立了一个在线的演示，可以直观的感受HTTP1.1与HTTP2.0的差距。

![HTTP/2 is the future of the Web, and it is here!](/files/-LhKPhIf-IA72hr-j2FR)

如果我们打开chrome的调试工具，切换到network选项，可以看到，同样是对390张图片的传输，HTTP 1.1 与 HTTP 2.0 有着明显的区别。

![HTTP 1.1](/files/-LhKPhIh6C9e7VycLWV1)

![HTTP 2.0](/files/-LhKPhIjK5G604jifknn)

## HTTP 1.1 VS HTTP 2.0

下面是查阅的一些资料，获取能让我们明白具体什么是HTTP2.0,以及它与HTTP1.1的区别。

* [HTTP/2](https://http2.github.io/)
* [Hypertext Transfer Protocol Version 2 (HTTP/2)(rfc7540)](https://httpwg.org/specs/rfc7540.html)
* [HTTP/2 简介](https://developers.google.com/web/fundamentals/performance/http2/?hl=zh-cn)
* \[http2讲解]<https://ye11ow.gitbooks.io/http2-explained/content/>
* [gRPC over HTTP2](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md)

## gRPC基于HTTP/2的优缺点

**优点**

* HTTP/2是一个经过实践检验的公开的标准
* 天然支持手机、物联网、浏览器
* 多语言实现容易，每种流行的编程语言都有自己的HTTP/2 Client
* HTTP/2支持Stream和流控
* 基于HTTP/2 在Gateway/Proxy很容易支持
* HTTP/2 安全性有保证
* HTTP/2 鉴权成熟

**缺点**

* rpc的元数据的传输不够高效
* HTTP/2 里一次gRPC调用需要解码两次,一次是HEADERS frame，一次是DATA frame
* HTTP/2 标准本身是只有一个TCP连接，但是实际在gRPC里是会有多个TCP连接，使用时需要注意

参考 [思考gRPC ：为什么是HTTP/2](https://blog.csdn.net/hengyunabc/article/details/81120904)


# 7、错误处理和Debug


# 8、gRPC身份验证


# 9、服务注册与发现

微服务的注册和发现是整个微服务架构中至关重要的一个环节，网上也有很多的关于注册发现的文章，因此我们在这里并不会过多的介绍。

同样，提起微服务的注册发现，很多人第一时间就会想到ZooKeeper，ETCD,eureka,Consul等众多组件。由于目前团队使用的Consul，同时最新版本的Consul也添加了对ServiceMesh的支持,这为我们接下来的ServiceMesh研究有参考价值，所以我们gRPC系列的服务注册和发现就以consul为主来进行介绍。

关于consul的原理和实际使用操作等内容，不是我们这次介绍的重点。后面有时间会详细学习一下consul的原理，然后另开一篇文章 [Architechture Consul](/architecture/infrastructure/consul) 来进行详细的原理介绍。

## Consul安装

为了便于测试，我们这里采用 consul on docker 的形式安装。可以参考 consul 在dockerhub上的文章 [Consul and Docker](https://hub.docker.com/_/consul)

### 1、将image pull 下来,采用最新版本就可以

```
docker pull consul
```

### 2、 启动一个consul server

```
docker run -d --name=dev-consul -p 8500:8500 -e CONSUL_BIND_INTERFACE=eth0 consul
```

这将运行一个完全基于内存的Consul服务器代理，其默认采用桥接网络，并且不在主机上显示任何服务。目前我的测试采用这种方式，但是不建议在生产中使用，因为一旦容器重启，所有的数据都会丢失。

查看一下容器启动的端口,并且对外暴露了8500 端口

```
$ docker container ls
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                                        NAMES
47b494323296        consul              "docker-entrypoint.s…"   11 seconds ago      Up 9 seconds        8300-8302/tcp, 8500/tcp, 8301-8302/udp, 8600/tcp, 8600/udp, 0.0.0.0:8500->8500/tcp   dev-consul
```

然后使用 <http://host-ip:8500> 就可以打开consul的UI界面了.

使用exec 命令可以进入到容器内部。

```
docker exec -it dev-consul bin/sh
```

使用下面的命令查看,consul在容器内绑定的地址。

```
$ docker exec -t dev-consul ifconfig
eth0      Link encap:Ethernet  HWaddr 02:42:AC:11:00:02
          inet addr:172.17.0.2  Bcast:172.17.255.255  Mask:255.255.0.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:22 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:1716 (1.6 KiB)  TX bytes:0 (0.0 B)

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:535 errors:0 dropped:0 overruns:0 frame:0
          TX packets:535 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1
          RX bytes:38800 (37.8 KiB)  TX bytes:38800 (37.8 KiB)
```

例如，如果该服务器在内部地址172.17.0.2上运行，则可以通过启动另外两个实例并告诉它们加入第一个节点来运行三节点集群以进行开发。

### 3、加入另外两个节点

```
$ docker run -d --name=dev-consul1 -e CONSUL_BIND_INTERFACE=eth0 consul agent -dev -join=172.17.0.2

$ docker run -d --name=dev-consul2 -e CONSUL_BIND_INTERFACE=eth0 consul agent -dev -join=172.17.0.2
```

查看集群中的所有成员

```
$ docker exec -t dev-consul consul members
Node          Address          Status  Type    Build  Protocol  DC   Segment
3a993c913dfa  172.17.0.3:8301  alive   server  1.5.1  2         dc1  <all>
47b494323296  172.17.0.2:8301  alive   server  1.5.1  2         dc1  <all>
7bc5b37a4e1a  172.17.0.4:8301  alive   server  1.5.1  2         dc1  <all>
```

### 4、客户端模式下运行Consul Agent

```
docker run -d  --name=dev-consul-agent   -e CONSUL_CLIENT_INTERFACE=eth0 consul agent  -retry-join=172.17.0.2
```

通过查看一下 `docker logs` 可以知道，这个agent已经能够进行注册发现的代理了。

```
$ docker logs 7509e2b8a31a

==> Found address '172.17.0.5' for interface 'eth0', setting client option...
==> Starting Consul agent...
==> Consul agent running!
           Version: 'v1.5.1'
           Node ID: 'a10e290d-9d1f-69a0-dc19-58c08fe7eaf3'
         Node name: '7509e2b8a31a'
        Datacenter: 'dc1' (Segment: '')
            Server: false (Bootstrap: false)
       Client Addr: [172.17.0.5] (HTTP: 8500, HTTPS: -1, gRPC: -1, DNS: 8600)
      Cluster Addr: 172.17.0.5 (LAN: 8301, WAN: 8302)
           Encrypt: Gossip: false, TLS-Outgoing: false, TLS-Incoming: false

==> Log data will now stream in as it occurs:

    2019/06/18 07:58:00 [INFO] serf: EventMemberJoin: 7509e2b8a31a 172.17.0.5
    2019/06/18 07:58:00 [INFO] agent: Started DNS server 172.17.0.5:8600 (udp)
    2019/06/18 07:58:00 [INFO] agent: Started DNS server 172.17.0.5:8600 (tcp)
    2019/06/18 07:58:00 [INFO] agent: Started HTTP server on 172.17.0.5:8500 (tcp)
    2019/06/18 07:58:00 [INFO] agent: started state syncer
    2019/06/18 07:58:00 [INFO] serf: EventMemberJoin: 47b494323296 172.17.0.2
    2019/06/18 07:58:00 [INFO] agent: Retry join LAN is supported for: aliyun aws azure digitalocean gce k8s mdns os packet scaleway softlayer triton vsphere
    2019/06/18 07:58:00 [INFO] consul: adding server 47b494323296 (Addr: tcp/172.17.0.2:8300) (DC: dc1)
    2019/06/18 07:58:00 [INFO] agent: Joining LAN cluster...
    2019/06/18 07:58:00 [INFO] agent: (LAN) joining: [172.17.0.2]
    2019/06/18 07:58:00 [WARN] manager: No servers available
    2019/06/18 07:58:00 [ERR] agent: failed to sync remote state: No known Consul servers
    2019/06/18 07:58:00 [INFO] serf: EventMemberJoin: 3a993c913dfa 172.17.0.3
    2019/06/18 07:58:00 [INFO] serf: EventMemberJoin: 7bc5b37a4e1a 172.17.0.4
    2019/06/18 07:58:00 [INFO] consul: adding server 3a993c913dfa (Addr: tcp/172.17.0.3:8300) (DC: dc1)
    2019/06/18 07:58:00 [INFO] consul: adding server 7bc5b37a4e1a (Addr: tcp/172.17.0.4:8300) (DC: dc1)
    2019/06/18 07:58:00 [INFO] agent: (LAN) joined: 1 Err: <nil>
    2019/06/18 07:58:00 [INFO] agent: Join LAN completed. Synced with 1 initial agents
    2019/06/18 07:58:00 [INFO] agent: Synced node info
```

此时此刻，我们再去查看一下 consul member 的话，可以看到有3个server一个client.

```
$ docker exec -t dev-consul consul members
Node          Address          Status  Type    Build  Protocol  DC   Segment
3a993c913dfa  172.17.0.3:8301  alive   server  1.5.1  2         dc1  <all>
47b494323296  172.17.0.2:8301  alive   server  1.5.1  2         dc1  <all>
7bc5b37a4e1a  172.17.0.4:8301  alive   server  1.5.1  2         dc1  <all>
7509e2b8a31a  172.17.0.5:8301  alive   client  1.5.1  2         dc1  <default>
```

consul 正常启动后，浏览器访问 ip:8500 就可以看到consul的UI界面了。

![consul UI界面](/files/-LhifOmzXIaNi5rpQmJO)

## 服务注册

接下来，我们正式进入gRPC 服务注册的过程了。

官方给出的例子中，已经进行了详细了案例介绍，我们可以根据官方的示例，写出我们的服务注册和发现 [grpc-examples](https://github.com/grpc/grpc-go/tree/master/examples)

我们以官方的helloword示例为例，进行注册与发现的改造。

首先在server端实现向consul中注册服务，并添加上health check

```go
// ConsulService 根据自己的需求进行的服务定制
type ConsulService struct {
    IP   string
    Port int
    Tag  []string
    Name string
}

//RegisterService 向consul中注册服务
func RegisterService(consulAddress string, service *ConsulService) {
    consulConfig := api.DefaultConfig()
    consulConfig.Address = consulAddress
    client, err := api.NewClient(consulConfig)
    if err != nil {
        log.Errorf("New consul client err \n: %v", err)
        return
    }

    agent := client.Agent()
    interval := time.Duration(10) * time.Second
    deregister := time.Duration(1) * time.Minute

    reg := &api.AgentServiceRegistration{
        ID:      fmt.Sprintf("%v-%v-%v", service.Name, service.IP, service.Port), // 服务节点的名称
        Name:    service.Name,                                                    // 服务名称
        Tags:    service.Tag,                                                     // tag，可以为空
        Port:    service.Port,                                                    // 服务端口
        Address: service.IP,                                                      // 服务 IP
        // In Consul 0.7 and later, checks that are associated with a service
        // may also contain this optional DeregisterCriticalServiceAfter field,
        // which is a timeout in the same Go time format as Interval and TTL. If
        // a check is in the critical state for more than this configured value,
        // then its associated service (and all of its associated checks) will
        // automatically be deregistered.
        Check: &api.AgentServiceCheck{ // 健康检查
            Interval:                       interval.String(),                                               // 健康检查间隔
            GRPC:                           fmt.Sprintf("%v:%v/%v", service.IP, service.Port, service.Name), // grpc 支持，执行健康检查的地址，service 会传到 Health.Check 函数中
            DeregisterCriticalServiceAfter: deregister.String(),                                             // 注销时间，相当于过期时间
        },
    }

    log.Printf("registing to %v\n", consulAddress)
    if err := agent.ServiceRegister(reg); err != nil {
        log.Printf("Service Register error\n%v", err)
        return
    }

}
```

## 服务发现

服务发现，在client端启动时，不再是直接去找server端进行通信，而是根据我们配置的consul的地址，进行服务发现，然后根据获取的服务地址进行与Server端的通信。

```go
package consul

import (
    "errors"
    "fmt"
    "regexp"
    "sync"

    "google.golang.org/grpc/serviceconfig"

    "github.com/hashicorp/consul/api"
    log "github.com/sirupsen/logrus"
    "google.golang.org/grpc/resolver"
)

const (
    defaultPort = "8500"
)

var (
    errMissingAddr = errors.New("consul resolver: missing address")

    errAddrMisMatch = errors.New("consul resolver: invalied uri")

    errEndsWithColon = errors.New("consul resolver: missing port after port-separator colon")

    regexConsul, _ = regexp.Compile("^([A-z0-9.]+)(:[0-9]{1,5})?/([A-z_]+)$")
)

// Init consul resolver
func Init() {
    log.Printf("calling consul init\n")
    resolver.Register(NewBuilder())
}

type consulBuilder struct {
}

type consulResolver struct {
    address              string
    wg                   sync.WaitGroup
    clientConn           resolver.ClientConn
    name                 string
    disableServiceConfig bool
    lastIndex            uint64
}

// NewBuilder new consulBuilder
func NewBuilder() resolver.Builder {
    return &consulBuilder{}
}

func (cb *consulBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOption) (resolver.Resolver, error) {

    log.Printf("calling consul build\n")
    log.Printf("target: %v\n", target)
    host, port, name, err := parseTarget(fmt.Sprintf("%s/%s", target.Authority, target.Endpoint))
    if err != nil {
        return nil, err
    }

    cr := &consulResolver{
        address:              fmt.Sprintf("%s%s", host, port),
        name:                 name,
        clientConn:           cc,
        disableServiceConfig: opts.DisableServiceConfig,
        lastIndex:            0,
    }

    cr.wg.Add(1)
    go cr.watcher()
    return cr, nil

}

func (cr *consulResolver) watcher() {
    log.Printf("calling consul watcher\n")
    config := api.DefaultConfig()
    config.Address = cr.address
    client, err := api.NewClient(config)
    if err != nil {
        log.Printf("error create consul client: %v\n", err)
        return
    }

    for {
        services, metainfo, err := client.Health().Service(cr.name, cr.name, true, &api.QueryOptions{WaitIndex: cr.lastIndex})
        if err != nil {
            log.Printf("error retrieving instances from Consul: %v", err)
        }

        cr.lastIndex = metainfo.LastIndex
        var newAddrs []resolver.Address
        for _, service := range services {
            addr := fmt.Sprintf("%v:%v", service.Service.Address, service.Service.Port)
            newAddrs = append(newAddrs, resolver.Address{Addr: addr})
        }
        log.Printf("adding service addrs\n")
        log.Printf("newAddrs: %v\n", newAddrs)

        serviceConfig, err := serviceconfig.Parse(cr.name)
        if err != nil {
            state := resolver.State{
                Addresses:     newAddrs,
                ServiceConfig: serviceConfig,
            }
            cr.clientConn.UpdateState(state)
        } else {
            log.Error(err.Error())
        }

    }

}

func (cb *consulBuilder) Scheme() string {
    return "consul"
}

func (cr *consulResolver) ResolveNow(opt resolver.ResolveNowOption) {
}

func (cr *consulResolver) Close() {
}

func parseTarget(target string) (host, port, name string, err error) {

    log.Printf("target uri: %v\n", target)
    if target == "" {
        return "", "", "", errMissingAddr
    }

    if !regexConsul.MatchString(target) {
        return "", "", "", errAddrMisMatch
    }

    groups := regexConsul.FindStringSubmatch(target)
    host = groups[1]
    port = groups[2]
    name = groups[3]
    if port == "" {
        port = defaultPort
    }
    return host, port, name, nil
}
```

## Try it

1. 启动我们的server端服务,然后查看Consul中是否有成功注册

```
$ go run server/cmd/main.go

INFO[0000] registing to 192.168.53.205:8500
INFO[0006] health checking
INFO[0016] health checking
INFO[0026] health checking
INFO[0036] health checking
```

![注册服务](/files/-LhifOn2A3cMGPQKWIo2)

1. 启动我们的客户端就能够输出下面的信息，这就成功发现了服务并发送了消息了,同时server端也能够收到相应的消息。

```
$ go run client/cmd/main.go
INFO[0000] calling consul init
INFO[0000] calling consul build
INFO[0000] target: {consul 192.168.53.205:8500 helloworld}
INFO[0000] target uri: 192.168.53.205:8500/helloworld
INFO[0000] calling consul watcher
INFO[0000] adding service addrs
INFO[0000] newAddrs: [{192.168.53.205:50051 0  <nil>}]
2019/06/19 14:26:49 Greeting: Hello world
```

[本示例完整的代码地址](https://github.com/PegasusMeteor/grpc-examples/tree/master/grpc-consul)

## 参考

* [用consul做grpc的服务发现](https://segmentfault.com/a/1190000018424798)
* [consul api](https://godoc.org/github.com/hashicorp/consul/api#Agent.ServiceRegister)


# 10、gRPC与gRPC Gateway


# 11、gRPC与分布式链路追踪

* [分布式链路追踪](https://www.selinux.tech/golang/grpc/pages/-LhmujxmzsdT8tG3kdXz#分布式链路追踪)
  * [尝鲜一下](https://www.selinux.tech/golang/grpc/pages/-LhmujxmzsdT8tG3kdXz#尝鲜一下httpsgithubcomopentracing-contribjava-opentracing-walkthrough)
    * [Setup MicroDonuts](/golang/grpc/grpc-tracing#setup-microdonuts)
    * [选择一个分布式追踪工具](https://www.selinux.tech/golang/grpc/pages/-LhmujxmzsdT8tG3kdXz#选择一个分布式追踪工具)
  * [官方示例 Tracing Demo](https://www.selinux.tech/golang/grpc/pages/-LhmujxmzsdT8tG3kdXz#官方示例-tracing-demo)
  * [gRPC 集成 Jaeger](https://www.selinux.tech/golang/grpc/pages/-LhmujxmzsdT8tG3kdXz#grpc-集成-jaeger)
    * [查看一下容器内的构成](https://www.selinux.tech/golang/grpc/pages/-LhmujxmzsdT8tG3kdXz#查看一下容器内的构成)
    * [tracing carrier](/golang/grpc/grpc-tracing#tracing-carrier)
    * [创建 GlobalTracer](https://www.selinux.tech/golang/grpc/pages/-LhmujxmzsdT8tG3kdXz#创建-globaltracer)
    * [gRPC Interceptor](/golang/grpc/grpc-tracing#grpc-interceptor)
    * [gRPC Client 和 gRPC Server 中集成 Interceptor 和Tracer](https://www.selinux.tech/golang/grpc/pages/-LhmujxmzsdT8tG3kdXz#grpc-client-和-grpc-server-中集成-interceptor-和tracer)
    * [运行一下](https://www.selinux.tech/golang/grpc/pages/-LhmujxmzsdT8tG3kdXz#运行一下)
  * [参考](https://www.selinux.tech/golang/grpc/pages/-LhmujxmzsdT8tG3kdXz#参考)

传送门 [Opentracing](/architecture/infrastructure/opentracing)

传送门 [Jayger && ZipKin](/architecture/infrastructure/jaeger-zipkin)

## [尝鲜一下](https://github.com/opentracing-contrib/java-opentracing-walkthrough)

在前面的两篇文章中，我们已经普及了什么是OpenTracing，以及Jaeger和ZipKin的简单比较。接下来，就来按照官方的例子进行一个简单的尝试，然后再抽丝剥茧，看看如何在我们的工程代码中进行实现。

[官方DEMO](https://github.com/opentracing-contrib/java-opentracing-walkthrough)

### Setup MicroDonuts

首先，测试环境需要安装JDK，以及Maven,可以搜索如何安装。

```
git clone git@github.com:opentracing-contrib/java-opentracing-walkthrough.git
cd java-opentracing-walkthrough/microdonuts
mvn package exec:exec
```

在浏览器中访问 <http://127.0.0.1:10001> 就可以看到一个简单的页面了。

![tracing demo](/files/-LhneV6i6Rc4taChYX-A)

### 选择一个分布式追踪工具

我们这里选择的是Jaeger.

修改 `microdonuts/tracer_config.properties` 文件：

```
tracer=jaeger
jaeger.reporter_host=localhost
jaeger.reporter_port=5775
```

在docker中运行：

```
docker run -d -p 5775:5775/udp -p 16686:16686 jaegertracing/all-in-one:latest
```

打开浏览器，访问 `http://localhost:16686` 就可以看到 Jaeger UI 了。

![Jaeger UI](/files/-LhneV6lX21S65GNX_US)

## 官方示例 Tracing Demo

[Take OpenTracing for a HotROD ride](https://medium.com/opentracing/take-opentracing-for-a-hotrod-ride-f6e3141f7941)

## gRPC 集成 Jaeger

在前面的对比中，我们已经大体上介绍过Jaeger 和 ZipKin 的区别，同时我们也介绍了二者在现有云原生生态中的发展。所以，我们这里选择了Jaeger来进行学习。

下面我们就介绍一下，如何在gRPC中集成Jaeger。

从我们之前介绍的Jaeger文章中，我们看到如果想要对每个服务进行tracing，除了需要在代码里面嵌入Jaeger Client 之外，还需要有Agent，collector，store,UI 等众多组件。我们这里只演示如何在代码中集成JaegerClient,至于其他组件，我们都在docker中运行。

```
docker run -d -p6831:6831/udp -p16686:16686 jaegertracing/all-in-one:latest
```

这时在浏览器中访问 `http://127.0.0.1:16686/search`就可以看到jaeger的页面了。不过现在代码还没有跑起来，看不到什么效果。

![Jaeger UI](/files/-LhzMA83RgJiRUHDfjK1)

### 查看一下容器内的构成

可以点击 [jaeger-docker-compose](https://github.com/jaegertracing/jaeger/tree/master/docker-compose) 去查看一下这个容器内的构成。 下面把代码贴一下。

```yaml
version: '2'

services:
    jaeger-collector:
      image: jaegertracing/jaeger-collector
      command: ["--cassandra.keyspace=jaeger_v1_dc1", "--cassandra.servers=cassandra", "--collector.zipkin.http-port=9411"]
      ports:
        - "14269"
        - "14268:14268"
        - "14267"
        - "14250"
        - "9411:9411"
      restart: on-failure
      depends_on:
        - cassandra-schema

    jaeger-query:
      image: jaegertracing/jaeger-query
      command: ["--cassandra.keyspace=jaeger_v1_dc1", "--cassandra.servers=cassandra"]
      ports:
        - "16686:16686"
        - "16687"
      restart: on-failure
      depends_on:
        - cassandra-schema

    jaeger-agent:
      image: jaegertracing/jaeger-agent
      command: ["--reporter.grpc.host-port=jaeger-collector:14250"]
      ports:
        - "5775:5775/udp"
        - "6831:6831/udp"
        - "6832:6832/udp"
        - "5778:5778"
      restart: on-failure
      depends_on:
        - jaeger-collector

    cassandra:
      image: cassandra:3.9

    cassandra-schema:
      image: jaegertracing/jaeger-cassandra-schema
      depends_on:
        - cassandra
```

即便对容器不是很熟悉的话，也能够看出，容器内运行的程序 包含了四个部分，分别是 `agent`,`query`,`collector`,还有存储`cassandra`。很明显，接下来，我们在 gRPC-example中，再集成 `jaeger-client`,然后就可以进行微服务的链路追踪了。

### tracing carrier

根据 Opentracing 的官方定义在进行，跨进程追踪调用的时候，需要进行 [Inject and Extract](https://opentracing.io/docs/overview/inject-extract/)。并且需要指定carrier。

而官方指定的carrier 只有两种。 [TextMapCarrier 和 HTTPHeadersCarrier](https://github.com/opentracing/opentracing-go/blob/master/propagation.go)

```go
// TextMapCarrier allows the use of regular map[string]string
// as both TextMapWriter and TextMapReader.
type TextMapCarrier map[string]string

// HTTPHeadersCarrier satisfies both TextMapWriter and TextMapReader.
//
// Example usage for server side:
//
//     carrier := opentracing.HTTPHeadersCarrier(httpReq.Header)
//     clientContext, err := tracer.Extract(opentracing.HTTPHeaders, carrier)
//
// Example usage for client side:
//
//     carrier := opentracing.HTTPHeadersCarrier(httpReq.Header)
//     err := tracer.Inject(
//         span.Context(),
//         opentracing.HTTPHeaders,
//         carrier)
//
type HTTPHeadersCarrier http.Header
```

当然，我们也可以进行 自定义的carrier，但是如果要实现自定义的carrier，就必须要实现 [TextMapWriter & TextMapReader](https://github.com/opentracing/opentracing-go/blob/master/propagation.go) 接口

```go
// TextMapWriter is the Inject() carrier for the TextMap builtin format. With
// it, the caller can encode a SpanContext for propagation as entries in a map
// of unicode strings.
type TextMapWriter interface {
    // Set a key:value pair to the carrier. Multiple calls to Set() for the
    // same key leads to undefined behavior.
    //
    // NOTE: The backing store for the TextMapWriter may contain data unrelated
    // to SpanContext. As such, Inject() and Extract() implementations that
    // call the TextMapWriter and TextMapReader interfaces must agree on a
    // prefix or other convention to distinguish their own key:value pairs.
    Set(key, val string)
}

// TextMapReader is the Extract() carrier for the TextMap builtin format. With it,
// the caller can decode a propagated SpanContext as entries in a map of
// unicode strings.
type TextMapReader interface {
    // ForeachKey returns TextMap contents via repeated calls to the `handler`
    // function. If any call to `handler` returns a non-nil error, ForeachKey
    // terminates and returns that error.
    //
    // NOTE: The backing store for the TextMapReader may contain data unrelated
    // to SpanContext. As such, Inject() and Extract() implementations that
    // call the TextMapWriter and TextMapReader interfaces must agree on a
    // prefix or other convention to distinguish their own key:value pairs.
    //
    // The "foreach" callback pattern reduces unnecessary copying in some cases
    // and also allows implementations to hold locks while the map is read.
    ForeachKey(handler func(key, val string) error) error
}
```

我们接下来来实现一下两个接口，采用一个自定义的carrier。

```go
// MDCarrier custome carrier
type MDCarrier struct {
    metadata.MD
}

// ForeachKey conforms to the TextMapReader interface.
// 这里必须要实现这个 TextMapReader 这个接口
// TextMapReader is the Extract() carrier for the TextMap builtin format. With it,
// the caller can decode a propagated SpanContext as entries in a map of
// unicode strings.
//type TextMapReader interface {
//    // ForeachKey returns TextMap contents via repeated calls to the `handler`
//    // function. If any call to `handler` returns a non-nil error, ForeachKey
//    // terminates and returns that error.
//    //
//    // NOTE: The backing store for the TextMapReader may contain data unrelated
//    // to SpanContext. As such, Inject() and Extract() implementations that
//    // call the TextMapWriter and TextMapReader interfaces must agree on a
//    // prefix or other convention to distinguish their own key:value pairs.
//    //
//    // The "foreach" callback pattern reduces unnecessary copying in some cases
//    // and also allows implementations to hold locks while the map is read.
//    ForeachKey(handler func(key, val string) error) error
//}
func (m MDCarrier) ForeachKey(handler func(key, val string) error) error {
    for k, strs := range m.MD {
        for _, v := range strs {
            if err := handler(k, v); err != nil {
                return err
            }
        }
    }
    return nil
}

// Set implements Set() of opentracing.TextMapWriter
// 这里也必须要实现
// TextMapWriter is the Inject() carrier for the TextMap builtin format. With
// it, the caller can encode a SpanContext for propagation as entries in a map
// of unicode strings.
//type TextMapWriter interface {
//    // Set a key:value pair to the carrier. Multiple calls to Set() for the
//    // same key leads to undefined behavior.
//    //
//    // NOTE: The backing store for the TextMapWriter may contain data unrelated
//    // to SpanContext. As such, Inject() and Extract() implementations that
//    // call the TextMapWriter and TextMapReader interfaces must agree on a
//    // prefix or other convention to distinguish their own key:value pairs.
//    Set(key, val string)
//}
func (m MDCarrier) Set(key, val string) {
    m.MD[key] = append(m.MD[key], val)
}
```

### 创建 GlobalTracer

创建 tracer 的过程可以参考 官方的demo示例 [jaeger-client-go](https://github.com/jaegertracing/jaeger-client-go/blob/master/config/example_test.go)

```go
// NewJaegerTracer NewJaegerTracer for current service
func NewJaegerTracer(serviceName string, jagentHost string) (tracer opentracing.Tracer, closer io.Closer, err error) {
    cfg := jaegercfg.Configuration{
        ServiceName: serviceName,
        Sampler: &jaegercfg.SamplerConfig{
            Type:  jaeger.SamplerTypeConst,
            Param: 1,
        },
        Reporter: &jaegercfg.ReporterConfig{
            LogSpans:            true,
            BufferFlushInterval: 1 * time.Second,
            LocalAgentHostPort:  jagentHost,
        },
    }
    // Example logger and metrics factory. Use github.com/uber/jaeger-client-go/log
    // and github.com/uber/jaeger-lib/metrics respectively to bind to real logging and metrics
    // frameworks.
    jLogger := jaegerlog.StdLogger
    jMetricsFactory := metrics.NullFactory

    // Initialize tracer with a logger and a metrics factory
    tracer, closer, err = cfg.NewTracer(
        jaegercfg.Logger(jLogger),
        jaegercfg.Metrics(jMetricsFactory))

    opentracing.SetGlobalTracer(tracer)
    if err != nil {
        grpclog.Errorf("Could not initialize jaeger tracer: %s", err.Error())
        return
    }
    return
}
```

### gRPC Interceptor

gRPC 提供了 拦截器，让我们可以在Clent端和server端对方法进行拦截处理，这样可以节省我们很大的麻烦。因为我们如果在server端和client端分别有很多的方法需要监控，难道我们每个方法都要去实现一遍 tracer定义？interceptor帮助我们解决了这个问题。

可以参考gRPC 官方 [Example Interceptor](https://github.com/grpc/grpc-go/tree/master/examples/features/interceptor)

下面我们看下如何定义的Client和Server Interceptor.

```go
// ClientInterceptor 客户端拦截器
// https://godoc.org/google.golang.org/grpc#UnaryClientInterceptor
func ClientInterceptor(tracer opentracing.Tracer) grpc.UnaryClientInterceptor {
    return func(ctx context.Context, method string, request, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {

        //一个RPC调用的服务端的span，和RPC服务客户端的span构成ChildOf关系
        var parentCtx opentracing.SpanContext
        parentSpan := opentracing.SpanFromContext(ctx)
        if parentSpan != nil {
            parentCtx = parentSpan.Context()
        }
        span := tracer.StartSpan(
            method,
            opentracing.ChildOf(parentCtx),
            opentracing.Tag{Key: string(ext.Component), Value: "gRPC Client"},
            ext.SpanKindRPCClient,
        )

        defer span.Finish()
        md, ok := metadata.FromOutgoingContext(ctx)
        if !ok {
            md = metadata.New(nil)
        } else {
            md = md.Copy()
        }

        err := tracer.Inject(
            span.Context(),
            opentracing.TextMap,
            MDCarrier{md}, // 自定义 carrier
        )

        if err != nil {
            log.Errorf("inject span error :%v", err.Error())
        }

        newCtx := metadata.NewOutgoingContext(ctx, md)
        err = invoker(newCtx, method, request, reply, cc, opts...)

        if err != nil {
            log.Errorf("call error : %v", err.Error())
        }
        return err
    }
}


// ServerInterceptor Server 端的拦截器
func ServerInterceptor(tracer opentracing.Tracer) grpc.UnaryServerInterceptor {
    return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
        md, ok := metadata.FromIncomingContext(ctx)
        if !ok {
            md = metadata.New(nil)
        }
        spanContext, err := tracer.Extract(
            opentracing.TextMap,
            MDCarrier{md},
        )

        if err != nil && err != opentracing.ErrSpanContextNotFound {
            grpclog.Errorf("extract from metadata err: %v", err)
        } else {
            span := tracer.StartSpan(
                info.FullMethod,
                ext.RPCServerOption(spanContext),
                opentracing.Tag{Key: string(ext.Component), Value: "gRPC Server"},
                ext.SpanKindRPCServer,
            )
            defer span.Finish()

            ctx = opentracing.ContextWithSpan(ctx, span)
        }

        return handler(ctx, req)

    }

}
```

### gRPC Client 和 gRPC Server 中集成 Interceptor 和Tracer

接下来，就可以在gRPC的代码中进行tracer 和 interceptor的集成了。

我们还是使用 前面 gRPC 注册consul 的例子，在这个例子的基础上，添加 tracer。

首先我们来看下 Clinet端

```go
func main() {
    consul.Init()

    tracer, closer, err := intercepter.NewJaegerTracer(serviceName, jaegerAgent)
    defer closer.Close()
    if err != nil {
        log.Printf("NewJaegerTracer err:", err.Error())
    }
    // Set up a connection to the server.
    ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
    conn, err := grpc.DialContext(ctx, consulService, grpc.WithInsecure(), grpc.WithBalancerName("round_robin"), grpc.WithUnaryInterceptor(intercepter.ClientInterceptor(tracer)))
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    c := pb.NewGopherClient(conn)

    ........
}
```

然后我们看下 Server 端

```go
func main() {

    tracer, closer, err := intercepter.NewJaegerTracer(serviceName, jaegerAgent)
    defer closer.Close()
    if err != nil {
        log.Printf("NewJaegerTracer err: %v", err.Error())
    }
    lis, err := net.Listen("tcp", port)
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    s := grpc.NewServer(grpc.UnaryInterceptor(intercepter.ServerInterceptor(tracer)))
    pb.RegisterGopherServer(s, &server{})
    grpc_health_v1.RegisterHealthServer(s, &HealthImpl{})
    RegisterToConsul()
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}
```

从上面的代码来看，我们的实现，非常简单。只要在client和server端启动时将我们的interceptor传入，同时传入创建好的trace就可以了。

但是有一点需要注意，因为我们使用的是interceptor，所以，在进行健康性检查的时候，也会被trace到。也就是说，我们在 jaeger UI 上查看时也能够看到 health check 的trace 信息。

### 运行一下

先运行 Server端，再启动Client端，就可以进行通信以及链路追踪了。

![gRPC Tracing](/files/-Li7Y32iHijN-v5IYYjD)

点开之后可以很详细的看到层级关系以及每个方法的信息，需要tracing的信息，还可以进行更详细的定义。

![gRPC Tracing](/files/-Li7Y32ntZa8nD8Fmhma)

还可以按照 调用链 的形式来进行查看。这有利于我们梳理清复杂的服务架构。

![gRPC Tracing](/files/-Li7Y32pkWpUtekSNhlU)

本文的示例 代码地址 [rpc-examples](https://github.com/PegasusMeteor/grpc-examples)

## 参考

* [grpc-jaeger](https://github.com/moxiaomomo/grpc-jaeger)
* [jaeger client libraries](https://www.jaegertracing.io/docs/1.12/client-libraries/)
* [opentracing-tutorial](https://github.com/yurishkuro/opentracing-tutorial)
* [Tracing HTTP request latency in Go with OpenTracing](https://medium.com/opentracing/tracing-http-request-latency-in-go-with-opentracing-7cc1282a100a)
* [cross process tracing](https://wu-sheng.gitbooks.io/opentracing-io/content/pages/api/cross-process-tracing.html)
* [Lesson 3 - Tracing RPC Requests](https://github.com/yurishkuro/opentracing-tutorial/tree/master/go/lesson03)
* [Opentracing Inject Extract](https://opentracing.io/docs/overview/inject-extract/)


# Scala

[可视化函数参考](https://superruzafa.github.io/visual-scala-reference/)


# 数据结构与算法


# 数组


# 队列


# 函数式编程


# 高阶函数

可以接收函数作为参数的函数就是高阶函数。

## Demo

```scala
object HighOrderFuncDemo01 {

  def main(args: Array[String]): Unit = {
    val res = test(sum, 2.0)
    println(res)

    val f1 = myPrint _
    f1()
  }

  def myPrint(): Unit = {
    println("Hello World!")
  }

  def test(f: Double => Double, n1: Double) = {
    f(n1)
  }

  def sum(double: Double) = {
    println("sum 被调用")
    double + double
  }
}
```

1. test 就是一个高阶函数
2. f: Double => Double 表示一个接收Double类型，并返回Double类型的函数
3. f(n1) 表示执行传入的函数。在这个示例中就是  `sum`。
4. `val f1 = myPrint _` 可以将函数直接赋值给一个变量。 后面的下划线 表示 这个函数不执行，只赋值。


# 偏函数

## 偏函数不是函数，而是一个triat

首先来看下偏函数的定义

## Demo

```scala
def main(args: Array[String]): Unit = {
    val list = List(1,3,4,5,"lits")


    // 输入的 是 Any ，返回的 Int
    // 如果返回true，则调用apply 创建对象实例，如果返回false，过滤
    val partialFunction = new PartialFunction[Any,Int] {
      override def isDefinedAt(x: Any): Boolean =  x.isInstanceOf[Int]

      // 对 传入 的值加 1 返回
      override def apply(v1: Any): Int = {
        v1.asInstanceOf[Int] + 1
      }
    }

    // 如果使用 偏函数，就要使用 collect
    // 遍历 list ，如果 true ，调用 apply，如果 false，不调用。
    val list2 = list.collect(partialFunction)
    println(list2)

  }
```

```scala
 def main(args: Array[String]): Unit = {

    val list = List(1,3,4.0,5,"lits")

    // 简写
    // isDefine apply 这两个函数 可以 不用写，直接写 case
    // case 后面的 表达式的意思 其实就相当于 isDefine 和 apply 的逻辑
    // 即 判断是否是某个类型，然后进行操作。
    // 这样的话，就可以写多个逻辑判断了
    def  f1:PartialFunction[Any,Int]={
        //   isDefine  apply
        case i:Int => i +1
        case j:Double => (j * 2).toInt
    }

    val list1 = list.collect(f1)
    println(list1)

    // 第二种简写形式,就是将 case 语句直接放入到 collect中。

    val list2 = list.collect{case i:Int => i +1}
    println(list2)

  }
```


# Immutable Collection


# List


# Mutable Collection


# Array


# 常见函数操作


# A


# aggregate

## definition

```scala
    abstract def aggregate[B](z: ⇒ B)(seqop: (B, A) ⇒ B, combop: (B, B) ⇒ B): B
```

## demo1

```scala
val donutBasket1: Set[String] = Set("Plain Donut", "Strawberry Donut")

// define an accumulator function to calculate the total length of the String elements
val donutLengthAccumulator: (Int, String) => Int = (accumulator, donutName) => accumulator + donutName.length

val totalLength = donutBasket1.aggregate(0)(donutLengthAccumulator, _ + _)
```

## demo2

```scala
val donutBasket2: Set[(String, Double, Int)] = Set(("Plain Donut", 1.50, 10), ("Strawberry Donut", 2.0, 10))

//define an accumulator function to calculate the total cost of Donuts
val totalCostAccumulator: (Double, Double, Int) => Double = (accumulator, price, quantity) => accumulator + (price * quantity)


val totalCost = donutBasket2.aggregate(0.0)((accumulator: Double, tuple: (String, Double, Int)) => totalCostAccumulator(accumulator, tuple._2, tuple._3), _ + _)
```


# andThen


# appended


# appendedAll


# C


# chain


# collect

## definition

```scala
def collect[B](pf: PartialFunction[A, B]): Traversable[B]
```

## demo

```scala
val donutNamesandPrices: Seq[Any] = Seq("Plain Donut", 1.5, "Strawberry Donut", 2.0, "Glazed Donut", 2.5)

// use collect function to cherry pick all the donut names
val donutNames: Seq[String] = donutNamesandPrices.collect{ case name: String => name }

// use collect function to cherry pick all the donut prices
val donutPrices: Seq[Double] = donutNamesandPrices.collect{ case price: Double => price }
```


# collectFirst


# combinations


# compose


# concat


# cond


# condOpt


# const


# contains


# containsSlice


# copyToArray


# corresponds


# count


# curried




---

[Next Page](/llms-full.txt/1)

