关于前端:Java与Go到底差别在哪谁要被时代抛弃

5次阅读

共计 18398 个字符,预计需要花费 46 分钟才能阅读完成。

👉腾小云导读

本文针对 Golang 与 Java 的根底语法、构造体函数、异样解决、并发编程及垃圾回收、资源耗费等各方面的差别进行比照总结。欢送浏览。

👉看目录,点珍藏

1 根底语法

1.1 变量

1.2 作用域规定

1.3 逗号 ok 模式

1.4 构造体、函数以及办法

1.5 值类型、援用类型以及指针

2 面向对象

2.1 Java 的 OOP 与 Golang 的构造体组合

2.2 侵入式与非侵入式接口

3 异样解决

3.1 Java 的异样解决

3.2 Golang 的异样解决

4. 并发编程

4.1 Java 和 Golang 的根本实现

4.2 Java 和 Golang 的区别

5 垃圾回收

5.1Java 的垃圾回收体系

5.2Golang GC 特色

6 资源耗费比照

6.1 Java 的 JIT 策略比 Golang 的 AOT 策略

6.2 内存调配和垃圾回收器

6.3 并发

6.4 反射

7 生态

8 总结

01、根底语法

Golang:  编码格调及可见域规定严格且简略;

Java:  来说档次接口清晰、标准。次要体现有以下几个:

1.1 变量

1.1.1 变量申明及应用

Java:变量能够申明了却不应用。

public static String toString(int num) {int data = num; return String.valueOf(num); }

Golang:申明的变量必须被应用,否则须要应用 \_来代替掉变量名,表明该变量不会比应用到。

func toString(num int) string {  
  data := num // data 没有使用者,无奈编译  
  return strconv.Itoa(num)  
}  
  
func toString(num int) string {  
  _ := num // 失常编译  
  return strconv.Itoa(num)  
}

1.1.2 变量申明及初始化

Java: 如果在办法外部申明一个变量但不初始化,在应用时会呈现编译谬误。

public void compareVariable() {  
        int age;  
        Object object;  
        System.out.println(age); // 编译谬误  
        System.out.println(object); // 编译谬误  
}

Golang: 对于根本类型来讲,申明即初始化;对于援用类型,申明则初始化为 nil。

func compareVariable() {
  var age int
  var hashMap *map[string]int
  fmt.Println(num) // num = 0
  fmt.Println(hashMap) // &hashMap== nil
}

1.2 作用域规定

Java:对办法、变量及类的可见域规定是通过 private、protected、public 关键字来管制的, 具体如下

Golang:管制可见域的形式只有一个,当字段首字母结尾是大写时阐明其是对外可见的、小写时只对包内成员可见。

1.3 逗号 ok 模式

在应用 Golang 编写代码的过程中,许多办法常常在一个表达式返回 2 个参数时应用这种模式:,ok,第一个参数是一个值或者 nil,第二个参数是 true/false 或者一个谬误 error。在一个须要赋值的 if 条件语句中,应用这种模式去检测第二个参数值会让代码显得优雅简洁。这种模式在 Golang 编码标准中十分重要。这是 Golang 本身的函数多返回值个性的体现。例如:

if _, ok := conditionMap["page"]; ok {//}

1.4 构造体、函数以及办法

1.4.1 构造体申明及应用

在 Golang 中区别与 Java 最显著的一点是:Golang 不存在“类”这个概念,组织数据实体的构造在 Golang 中被称为构造体。

函数能够脱离“类”而存在,函数能够依赖于构造体来调用或者依赖于包名调用。Golang 中的构造体放弃了继承、实现等多态概念,构造体之间可应用组合来达到复用办法或者字段的成果。

Golang 申明一个构造体并应用:

// User 定义 User 构造体
type User struct {
  Name string
  Age int
}

// 应用一个构造体
func main() {personPoint := new(User) // 通过 new 办法创立构造体指针
  person1 := User{} // 通过 Person{}创立默认字段的构造体
  person2 := User{
    Name: "xiaoHong",
    Age: 21,
  }
  fmt.Println(personPoint) // &{0}
  fmt.Println(person1) // {0}
  fmt.Println(person2) // {xiaoHong 21}
}

Java 申明实体并应用:

class User {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void setName(String name) {this.name = name;}
    public void setAge(int age) {this.age = age;}
    public String getName() {return name;}
    public int getAge() {return age;}
    public String print() {return "{name =" + name + ",age =" + age + "}";
    }
}

public class Demo {public static void main(String[] args) {User user = new User("xiaohong", 29);
        System.out.println("user 信息:" + user.print());
    }
}
// 执行后果
user 信息:{name = xiaohong,age = 29}

1.4.2 函数和办法的区别

在 Java 中: 有的“函数”都是基于“类”这个概念构建的,也就是只有在“类”中才会蕴含所谓的“函数”,这里的“函数”被称为“办法”,可见上方申明实体并应用。

在 Golang 中:“函数”和“办法”的最根本区别是:函数不基于构造体而是基于包名调用,办法基于构造体调用。如下实例:

package entity

import "fmt"

type User struct {
  Name string
  Age int
}

// User 构造体 / 指针可调用的 "办法",属于 User 构造体
func (user *User) Solve() {fmt.Println(user)
}

// 任何中央都可调用的 "函数",不属于任何构造体,可通过 entity.Solve 调用
func Solve(user *User) {fmt.Println(user)
}
func main() {userPoint := new(entity.User) // 通过 new 办法创立构造体指针
  entity.Solve(userPoint) // 函数调用
  userPoint.Solve() // 办法调用}

1.5 值类型、援用类型以及指针

Java:在 Java 中不存在显式的指针操作;8 种根本数据类型是值类型,数组和对象属于援用类型。

Golang:而 Golang 中存在显式的指针操作,然而 Golang 的指针不像 C 那么简单,不能进行指针运算。

所有的根本类型都属于值类型,然而有几个类型比拟非凡,体现出援用类型的特色,别离是 slice、map、channel、interface。除赋值以外它们都能够当做援用类型来应用。因而当咱们这样做时,能够间接应用变量自身而不必指针。

slice 与数组的区别为是否有固定长度,slice 无固定长度,数组有固定长度。值得注意的是,在 Golang 中只有同长度、同类型的数组才可视为“同一类型”,譬如 []int 和 [3]int 则会被视为不同的类型,这在参数传递的时候会造成编译谬误。

1.5.1 数组比照

在 Java 中 当向办法中传递数组时,能够间接通过该传入的数组批改原数组外部值(浅拷贝)。

在 Golang 中 则有两种状况:在不限定数组长度 (为 slice) 时也间接扭转原数组的值,当限定数组长度时会齐全复制出一份正本,来进行批改(深拷贝)。

Java 的数组实际:

    public static void main(String[] args) {int[] array = {1, 2, 3};
        change(array);
        System.out.println(Arrays.toString(array)); // -1,2,3
    }

    private static void change(int[] array) {array[0] = -1;
    }

Golang 的数组实际:

// 不限定长度(即 slice):
func main() {var array = []int{1, 2, 3}
  change(array)
  fmt.Println(array) // [-1 2 3]
}

func change(array []int) {array[0] = -1
} 
// 限定长度(即数组):func main() {var array = [3]int{1, 2, 3}
  change(array)
  fmt.Println(array) //[1 2 3]
}

func change(array [3]int) {array[0] = -1
}

1.5.2 对象比照

在 Golang 中:传入函数参数的是原对象的一个全新的 copy(有本人的内存地址); go 对象之间赋值是把对象内存的 内容(字段值等)copy 过来,所以才会看到 globalUser 批改前后的地址不变,然而对象的内容变了。

在 Java 中:传入函数参数的是原对象的援用的 copy(指向的是同样的内存地址); Java 对象之间的赋值是把对象的援用  copy 过来,因为援用指向的地址变了,所以对象的内容也变了。

Golang 的对象实际:

//User 定义 User 构造体
type User struct {
   Name string
   Age int
}

// 定义一个全局的 User
var globalUser = User {
   "xiaoming",
   28,
}

// modifyUser 定义一个函数,参数为 User 构造体“对象”,将全局 globalUser 指向传递过去的 User 构造体“对象”func modifyUser(user User) {fmt.Printf("参数 user 的地址 = %p\n",&user)
   fmt.Printf("globalUser 批改前的地址 = %p\n",&globalUser)
   fmt.Println("globalUser 批改前 =",globalUser)
   // 批改指向
   globalUser = user
   fmt.Printf("globalUser 批改后的地址 = %p\n",&globalUser)
   fmt.Println("globalUser 批改后 =",globalUser)
}

func main() {
   var u User = User {
      "xiaohong",
      29,
   }
   fmt.Printf("将要传递的参数 u 的地址 = %p\n",&u)
   modifyUser(u)
}

// 执行后果
将要传递的参数 u 的地址 = 0xc0000ac018
参数 user 的地址 = 0xc0000ac030
globalUser 批改前的地址 = 0x113a270
globalUser 批改前 = {xiaoming 28}
globalUser 批改后的地址 = 0x113a270
globalUuser 批改后 = {xiaohong 29}
class User {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void setName(String name) {this.name = name;}
    public void setAge(int age) {this.age = age;}
    public String getName() {return name;}
    public int getAge() {return age;}
    public String print() {return "{name =" + name + ",age =" + age + "}";
    }
}

public class Demo {private static User globalUser = new User("xiaoming",28);
    public static void modifyUser(User user) {System.out.println("参数 globalUser 的地址 =" + user);
        System.out.println("globalUser 批改前的地址 =" + globalUser);
        System.out.println("globalUser 批改前 =" + globalUser.print());
        globalUser = user;
        System.out.println("globalUser 批改后的地址 =" + globalUser);
        System.out.println("globalUser 批改后 =" + globalUser.print());
    }
    public static void main(String[] args) {User user = new User("xiaohong", 29);
        System.out.println("将要传递的参数 user 的地址 =" + user);
        modifyUser(user);
    }
}

// 执行后果
将要传递的参数 user 的地址 = com.example.demo.User@5abca1e0
参数 globalUser 的地址 = com.example.demo.User@5abca1e0
globalUser 批改前的地址 = com.example.demo.User@2286778
globalUser 批改前 = {name = xiaoming,age = 28}
globalUser 批改后的地址 = com.example.demo.User@5abca1e0
globalUser 批改后 = {name = xiaohong,age = 29}

1.5.3 指针的区别

在 Java 中:如果传递了援用类型(对象、数组等)会复制其指针进行传递

在 Golang 中:必须要显式传递 Person 的指针,不然只是传递了该对象的一个正本。

Golang 的指针:

// User 定义 User 构造体
type User struct {
  Name string
  Age int
}

func main() {
  p1 := User{
    Name: "xiaohong",
    Age: 21,
  }
  changePerson(p1)
  fmt.Println(p1.Name) // xiaohong
  changePersonByPointer(&p1)
  fmt.Println(p1.Name) // xiaoming
}

func changePersonByPointer(user *User) {user.Name = "xiaoming"}

func changePerson(user User) {user.Name = "xiaoming"}

Java 的指针:

public class Demo {public static void changePerson(User user) {user.setName("xiaoming");
    }
    public static void main(String[] args) {User user = new User("xiaohong", 29);
        changePerson(user);
        System.out.println("user 信息:" + user.getName()); // xiaoming
    }

}


class User {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void setName(String name) {this.name = name;}
    public void setAge(int age) {this.age = age;}
    public String getName() {return name;}
    public int getAge() {return age;}
}

02、面向对象

在 Golang 中:没有明确的 OOP 概念,Go 语言只提供了两个要害类型:struct,interface。

在 Java 中:面向对象语言的封装、继承、多态的个性以及“继承(extends)、实现(implements)”等关键字。

2.1 Java 的 OOP 与 Golang 的构造体组合

假如有这么一个场景:动物(Animal)具备名字(Name)、年龄(Age)的根本个性,当初须要实现一个狗(Dog),且 Dog 须要具备  Animal 所需的所有个性,并且本身具备犬吠(bark ())的动作。

首先来看看最相熟的 Java 要如何写,很简略,应用抽象类形容 Animal 作为所有动物的超类,Dog extends Animal:

public abstract class Animal {
    String name;
    int age;
}

public class Dog extends Animal {public void bark() {System.out.println(age + "岁的" + name + "在汪汪叫");
    }
}

public class Demo {public static void main(String[] args) {Dog dog = new Dog();
        dog.name = "小龙";
        dog.age = 2;
        dog.bark(); // 2 岁的小龙在汪汪叫}
}

在 Golang 中,能够这样通过构造体的组合来实现:

package main

import "fmt"

type Animal struct {
  Name string
  Age int
}

type Dog struct {*Animal}

func (dog *Dog) Bark() {fmt.Printf("%d 岁的 %s 在汪汪叫", dog.Age, dog.Name)
}

func main() {
  dog := &Dog{&Animal{
    Name: "小龙",
    Age: 2,
  }}
  dog.Bark() // 2 岁的小龙在汪汪叫...}

2.2 侵入式与非侵入式接口

在 Java 中:接口次要作为不同组件之间的契约存在。对契约的实现是强制的,你必须申明你确实实现了该接口。

这类接口咱们称为侵入式接口。“侵入式”的次要体现在于,实现类须要明确申明本人实现了某个接口。

在 Golang 中:非侵入式接口不须要通过任何关键字申明类型与接口之间的实现关系,只有一个类型实现了接口的所有办法,那么这个类型就是这个接口的实现类型。

Java:治理狗的行为,能够通过以下接口实现。

public interface Dog {void Bark();
}

public class DogImpl implements Dog{

    @Override
    public void Bark() {System.out.println("汪汪叫");
    }
}

public class Demo {public static void main(String[] args) {Dog dog = new DogImpl();
        dog.Bark(); // 汪汪叫}

}

Golang : 假如当初有一个 Factory 接口,该接口中定义了 Produce() 办法及  Consume() 办法,CafeFactory 构造体作为其实现类型。那么能够通过以下代码实现。

package entity

type Factory interface {Produce() bool
  Consume() bool}

type CarFactory struct {ProductName string}

func (c *CarFactory) Produce() bool {fmt.Printf("CarFactory 生产 %s 胜利", c.ProductName)
  return true
}

func (c *CarFactory) Consume() bool {fmt.Printf("CarFactory 生产 %s 胜利", c.ProductName)
  return true
}

// --------------
package main

func main() {factory := &entity.CarFactory{"Car"}
  doProduce(factory)
  doConsume(factory)
}


func doProduce(factory entity.Factory) bool {return factory.Produce()
}

func doConsume(factory entity.Factory) bool {return factory.Consume()
}

Golang 的非侵入式接口长处:

简略、高效、按需实现,具体来说如下:

在 Go 中,类没有继承的概念,只须要晓得这个类型实现了哪些办法,每个办法是啥行为。

实现类型的时候,只须要关怀本人应该提供哪些办法,不必再纠结接口须要拆得多细才正当。接口由应用方按需定义,而不必事先布局。

缩小包的引入,因为多援用一个内部的包,就意味着更多的耦合。接口由应用方按本身需要来定义,应用方无需关怀是否有其余模块定义过相似的接口。

Java 的侵入式接口长处:

层次结构清晰,对类型的动作行为有严格的治理。

03、异样解决

在 Java 中:  通过 try..catch..finally 的形式进行异样解决,有可能出现异常的代码会被 try 块给包裹起来,在 catch 中捕捉相干的异样并进行解决,最初通过 finally 块来对立执行最初的完结操作(开释资源)。

在 Golang 中:错误处理形式有两种形式:, ok 模式  与 defer、panic 及 recover 的组合。

3.1 Java 的异样解决

public class ExceptionTest {public static void main(String[] args) {
        FileInputStream fileInputStream = null;
        try{fileInputStream = new FileInputStream("test.txt");
        }catch (IOException e){System.out.println(e.getMessage());
            e.printStackTrace();
            return;
        }finally {if(fileInputStream!=null){
                try {fileInputStream.close();
                } catch (IOException e) {e.printStackTrace();
                }
            }
            System.out.println("回收资源");
        }
    }
}

3.2 Golang 的异样解决

Golang 的, ok 模式。所有可能出现异常的办法或者代码间接把谬误当作第二个响应值进行返回,程序中对返回值进行判断,非空则进行解决并且立刻中断程序的执行。

长处:这种比 Java 的简略很多,是 Golang 在异样解决形式上的一大特色。

毛病:代码冗余,所有的异样都须要通过 if err != nil {} 去做判断和解决,不能做到对立捕获和解决,容易脱漏。

func main() {value, err := Bark()

  if err != nil {
    // 返回了异样,进行解决
    log.error("... 异样:", err)
    return err
  }

  // Bark 办法执行正确,继续执行后续代码
  Process(value)
}
  • Golang 的 defer、panic 及 recover

defer 是 Golang 错误处理中罕用的关键字,pannic 及 recover 是 Golang 中的内置函数,通常与 defer 联合进行错误处理,它们各自的用处为:

defer 的作用是提早执行某段代码,个别用于敞开资源或者执行必须执行的收尾操作,无论是否呈现谬误 defer 代码段都会执行,相似于 Java 中的  finally 代码块的作用;defer 也能够执行函数或者是匿名函数:

defer func() {// 清理工作} ()

// 这是传递参数给匿名函数时的写法
var num := 1
defer func(num int) {// 做你简单的清理工作} (num)

须要留神的是,defer 应用一个栈来保护须要执行的代码,所以 defer 函数所执行的程序是和 defer 申明的程序相同的:

defer fmt.Println(a)
defer fmt.Println(b)
defer fmt.Println(c)

执行后果:c
b
a

panic 的作用是抛出谬误,制作零碎运行时恐慌。当在一个函数执行过程中调用 panic ()函数时,失常的函数执行流程将立刻终止。但函数中之前应用 defer 关键字提早执行的语句将失常开展执行,之后该函数将返回到调用函数,并导致逐层向上执行 panic 流程,直至所属的 goroutine 中所有正在执行的函数被终止,panic 和 Java 中的 throw 关键字相似:用于抛出谬误,阻止程序执行。

recover 的作用是捕获 panic 抛出的谬误并进行解决,须要联结 defer 来应用,相似于 Java 中的 catch 代码块:

func main() {fmt.Println("main begin")
      // 必须要先申明 defer,否则不能捕捉到 panic 异样
      defer func() {fmt.Println("defer begin")
        if err := recover(); err != nil {
            // 这里的 err 其实就是 panic 传入的内容
            fmt.Println(err)
        }
         fmt.Println("defer end")
      }()
      test()
      // test 中呈现谬误,这里开始上面代码不会再执行
      fmt.Println("main end")
}

func test() {fmt.Println("test begin")
   panic("error")
   // 这里开始上面代码不会再执行
   fmt.Println("test end")
}


// 执行后果
main begin
test begin
defer begin
error
defer end

注:利用 recover 解决 panic 指令,defer 必须在 panic 之前申明,否则当 panic 时,recover 无奈捕捉到 panic。

04、并发编程

Java 中 CPU 资源分配对象是 Thread,Go 中 CPU 资源分配对象是 goroutine。Java Thread 与零碎线程为一一对应关系,goroutine 是 Go 实现的用户级线程,与零碎线程是 m:n 关系。

4.1 Java 和 Golang 的根本实现

在 Java 中,如要取得 CPU 资源并异步执行代码单元,须要将代码单元包装成 Runnable,并创立能够运行代码单元的 Thread , 执行 start 办法启动线程:

Runnable task = ()-> System.out.println("task running");
Thread t = new Thread(task);
t.start();

Java 利用个别应用线程池集中处理工作,以防止线程重复创立回收带来的开销:

Runnable task = ()-> System.out.println("task running");
Executor executor = Executors.newCachedThreadPool();
executor.execute(task);

在 Golang 中,则须要将代码包装成函数。应用 go 关键字调用函数之后,便创立了一个能够运行代码单元的 goroutine。一旦 CPU 资源就绪,对应的代码单元便会在 goroutine 中执行:

go func() {fmt.Println("test task running")
}()

4.2 Java 和 Golang 的区别

Golang 语言采纳了 CSP(Communicating Sequential Processes)的模型,其中以 goroutine 和 channel 作为次要实现伎俩。Java 则采纳了多线程模型,其中以 Thread 和 Synchronization 作为次要实现伎俩。

Golang 语言的 goroutine 是一种轻量级的线程,它们的创立和销毁速度比 Java 中的线程快得多。在 Java 中,创立和销毁线程都须要相当大的开销。

Golang 语言的 channel 是一种同步数据传递的机制,它能够不便地解决多道程序之间的通信问题。Java 中则须要应用同步工具(如 Semaphore、CountDownLatch 等)来解决多线程之间的通信问题。

Java 和 Go 官网库中同步形式的对应关系表

4.2.1 Java synchronized 与 Golang Mutex

Java synchronized:线程 A 在 t1 时刻开释 JVM 锁后(monitor exit),在随后的 t2 时刻,若任意线程 B 获取到 JVM 锁(monintor enter),则线程 A 在 t1 时刻之前产生的所有写入均对 B 可见。synchronized 是 JVM 内置锁实现,写入 volatile 变量相当于 monitor exit,读取 volatile 变量相当于 monintor enter。即一把锁只能同时被一个线程获取,没有取得锁的线程只能阻塞期待。

synchronized 的应用:

润饰一个代码块,被润饰的代码块称为同步代码块,作用范畴是大括号 {} 括起来的代码:

public void method()
{synchronized(this) {// todo some thing}
}

润饰一个办法,被润饰的办法称为同步办法,其作用范畴是整个办法:

public synchronized void method()
{// todo some thing}

批改一个静态方法,作用范畴是整个静态方法:

public synchronized static void method() {// todo some thing}

批改一个类,作用范畴是 synchronized 前面括号括起来的局部:

class DemoClass {public void method() {synchronized(DemoClass.class) {// todo some thing}
   }
}

Go Mutex:Go 并未像 Java 一样提供 volatile 这样根底的关键字,但其 Mutex 相干内存模型和 synchronized 或 Java 官网库 Lock 实现有非常靠近语义。若 goroutine A 在 t1 时刻开释 sync.Mutex 或 sync.RWMutex 后,在随后的 t2 时刻,若任意 goroutine B 获取到锁,则 goroutine A 在 t1 时刻之前产生的所有写入均对 B 可见。

Mutex 的应用:润饰要害代码:每次只有一个线程对这个要害变量进行批改,防止多个线程同时这个要害代码进行操作。

func main() {

  var mutex sync.Mutex

  count := 0

  for i := 0; i < 100; i++ {go func() {mutex.Lock() // 加锁
      count += 1
      mutex.Unlock() // 解锁}()}

  // 休眠,期待 2s
  time.Sleep(time.Second * 2)
  // 100,没有加锁后果不正确
  fmt.Println("count =", count)
}

润饰构造体:带锁构造体初始化后,间接调用对应的线程平安函数就能够。

type count struct {
  lock sync.Mutex
  value int
}

// 构造体对应的构造办法
func (receiver *count) countOne() {receiver.lock.Lock()
  defer receiver.lock.Unlock()
  receiver.value++
}

func main() {

  c := count{lock: sync.Mutex{},
    value: 0,
  }
  group := sync.WaitGroup{}
  for i := 0; i < 10; i++ {group.Add(1)
    go func(count2 *count) {defer group.Done()
      for i := 0; i < 100; i++ {count2.countOne()
      }
    }(&c)
  }
  group.Wait()
  fmt.Printf("The count value is %d", c.value) // The count value is 1000
}

4.2.2 条件变量

  • Java 和 Golang 类似点

一般来说,条件变量衍生于锁,不同条件变量只是同一锁空间下的不同期待队列。Java 能够应用 synchronized 代码块爱护特定代码门路,兼而能够在 synchronized 代码块中应用 Object wait 和 notify、notifyall 办法实现繁多条件期待。如果须要多个条件,能够应用官网库提供的 Lock 实现和 Condition 实现。

  • Java 和 Golang 区别点

Java 创立条件变量的形式是调用 Lock 接口 newCondition 办法。Go sync.Cond 构造体需设置 sync.Mutex 字段能力工作,挂起办法为 Wait,唤醒办法为 Braodcast。

Go 语言外面条件变量的告诉 Signal() 和 Broadcast(),并没有在锁的爱护下执行,而是在 Unlock() 之后执行。

4.2.3 CAS/Atomic

原子性

一个或者多个操作在 CPU 执行的过程中不被中断的个性,称为原子性(atomicity)。

CAS 是乐观锁技术,当多个线程尝试应用 CAS 同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并能够再次尝试。Java 和 Go 均反对 CAS 及原子操作。

在 Java 中:CAS 操作由 volatile 关键字和 VarHandle(9 之前是 UnSafe)反对,在此基础上有了 Atomic 类和并发包中的大量无锁实现(如 ConcurrentHashMap, AQS 队列等)。

在 Golang 中:atomic.Value 提供了 CAS 操作根底,它保障任意类型(interface {}) 的 Load 和 Store 为原子操作,在此基础上有 atomic 包。

4.2.4 Once 与单例模式

sync.Once 是 Golang 规范库提供的使函数只执行一次的实现,常利用于单例模式,例如初始化配置、放弃数据库连贯等。它有 2 个个性:

1、保障程序运行期间某段代码只会执行一次;

2、如果多个 goroutine 同时执行 Once 守护代码,只有 1 个 goroutine 会取得执行机会,其余 goroutine 会阻塞直至代码执行结束。

func main() {var once = sync.Once{}
  f := func() {time.Sleep(10 * time.Millisecond)
    fmt.Println("do once")
  }
  go func() {fmt.Println("do once start")
    once.Do(f)
    fmt.Println("do once finish")
  }()
  time.Sleep(1 * time.Millisecond)
  for i := 0; i < 2; i++ {go func() {fmt.Println("block...")
      once.Do(f)
      fmt.Println("resume")
    }()}
  time.Sleep(10 * time.Millisecond)
}//~
do once start
block...
block...
do once
do once finish
resume
resume

java 中单例模式的写法有好几种,次要是懒汉式单例、饿汉式单例。

懒汉式单例: 懒汉式单例的实现没有思考线程平安问题,须要联合 synchronized,保障线程平安。

// 懒汉式单例类. 在第一次调用的时候实例化本人
public class Singleton {private Singleton() {}
    private static Singleton single=null;
    // 动态工厂办法
    public static synchronized Singleton getInstance() {if (single == null) {single = new Singleton();
         }
        return single;
    }
}

饿汉式单例:饿汉式在类创立的同时就曾经创立好一个动态的对象供零碎应用,当前不再扭转,所以天生是线程平安的。

// 饿汉式单例类. 在类初始化时,曾经自行实例化
public class Singleton {private Singleton() {}
    private static final Singleton single = new Singleton();
    // 动态工厂办法
    public static Singleton getInstance() {return single;}
}

05、垃圾回收

GC(Garbage Collection)垃圾回收是一种主动治理内存的形式,反对 GC 的语言无需手动治理内存,程序后盾主动判断对象是否存活并回收其内存空间,使开发人员从内存治理上解脱进去。

因为反对更多的个性和更灵活多样的 GC 策略, 比方分代, 对象可挪动, 各种参数调节等等. 而 Go 只做了一种 GC 计划, 不分代, 不可挪动, 没什么参数能调节, 而且更重视暂停工夫的优化, 执行 GC 的机会更频繁, 所以 Go 通常更占更少的内存, 但代价就是 GC 性能比 JVM 差了不少。

5.1 Java 的垃圾回收体系

Java 基于 JVM 实现了垃圾收集的性能,其体系很宏大,包含了垃圾回收器(G1、CMS、Serial、ParNew 等)、垃圾回收算法(标记 - 革除、标记 - 整顿、复制、分代收集)、可达性算法(可达性剖析、援用计数法)、援用类型、JVM 内存模型等内容。

通过多代倒退,Java 的垃圾回收机制较为欠缺,Java 划分新生代、老年代来存储对象。对象通常会在新生代分配内存,屡次存活的对象会被移到老年代,因为新生代存活率低,产生空间碎片的可能性高,通常选用“标记 - 复制”作为回收算法,而老年代存活率高,通常选用“标记 - 革除”或“标记 - 整顿”作为回收算法,压缩整顿空间。

5.2 Golang GC 特色

三色标记、并发标记和打扫、非分代、非压缩、写屏障

5.2.1 三色标记

程序开始时有黑白灰三个汇合,初始时所有对象都是红色;

从 root 对象开始标记,将所有可达对象标记为灰色;

从灰色对象汇合取出对象,将其援用对象标记为灰色,放入灰色汇合,并将本人标记为彩色;

反复第三步,直到灰色汇合为空,即所有可达对象全副都被标记;

标记完结后,不可达红色对象即为垃圾,对内存进行迭代打扫,回收红色对象;

重置 GC 状态。

5.2.2 非分代

Java 采纳分代回收(依照对象生命周期长短划分不同的代空间,生命周期长的放入老年代,短的放入新生代,不同代有不同的回收算法和回收频率),Golang 没有分代,厚此薄彼。

5.2.3 非压缩

在垃圾回收之后不会进行内存整理以革除内存碎片。

5.2.4 写屏障

在并发标记的过程中,如果利用程序修改了对象图,就可能呈现标记脱漏的可能,写屏障是为了解决标记脱漏的问题。

06、资源耗费比照

在内存利用效率上,Go 语言的确比 Java 做得更好。咱们 4 个不同的角度来总结:

6.1 Java 的 JIT 策略比 Golang 的 AOT 策略

Java 在运行时相比 Golang 多占用了一些内存。起因在于:

Java 运行态中蕴含了一个残缺的解释器、一个 JIT 编译期以及一个垃圾回收器,这会显著地减少内存。Golang 语言间接编译到机器码,运行态只蕴含机器码和一个垃圾回收器。

因而 Golang 的运行态绝对耗费内存较少。

### 6.2 内存调配和垃圾回收器

Java 的确在起步占用上偏多,毕竟 jvm 须要更多内存做 jit,默认的 gc 算法对内存要求偏高,但这不能代表后续占用依然线性增长。

如果指标是启动成千盈百个内存需要较少的过程,那 Java 的确不善于。

6.3 并发

协程模型比线程模型更加节俭内存。

6.4 反射

Golang 的反射更加简略,导致框架的内存耗费 Golang 程序比 Java 程序优良。次要是因为:

Java 的框架实现中大量应用反射,并应用 hashmap 缓存信息,这 2 个都是极度耗费内存的行为。

Golang 的框架中也应用 reflect、map。然而 Golang 是面向 interface 和值类型的,这导致 Golang 的反射模型要比 Java 的反射模型简略十分多,反射过程要产生的对象数量也少十分多。

07、生态

有人说,Java 在生态这方面几乎是无敌的存在。这次要得益于 Spring 全家桶,Spring 让 Java 走上了神座。

Golang 语言出名的框架也很多,然而目前远远没有 Spring 影响那么大。

08、总结

最初,上表为各位总结~

整体来说,二者各有优劣,各位自取所需。以上是本次分享全部内容,欢送大家在评论区分享交换。如果感觉内容有用,欢送转发~

-End-

原创作者|宋欣东

技术责编|宋欣东

JAVA 与 GO,孰是孰非?欢送公众号评论区留言分享你的观点。咱们将选取 1 则最有创意的分享,送出腾讯云开发者 - 限定随行杯 1 个(见下图)。5 月 12 日中午 12 点开奖。

公众号后盾回复「福利」,一键领腾讯 GO 语言设计模式教程、GO 整洁架构教程、GPT 利用教程文集

正文完
 0