Java与Go到底差别在哪,谁要被时代抛弃?

Java与Go到底差别在哪,谁要被时代抛弃?

腾小云导读

本文针对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 :变量能够声明了却不运用。

publicstaticString toString(intnum){ intdata = num; returnString.valueOf(num); }

Golang:声明的变量必须被运用,不然需求运用_来代替掉变量名,标明该变量不会比运用到。

functoString(numint)string{
data := num// data没有运用者,无法编译  
returnstrconv.Itoa(num)  
}  
functoString(numint)string{  
_ := num// 正常编译  
returnstrconv.Itoa(num)  
}

1.1.2 变量声明及初始化

Java : 假如在办法内部声明一个变量但不初始化,在运用时会呈现编译过错。

publicvoidcompareVariable(){
intage;  
Objectobject;  
System.out.println(age);// 编译过错  
System.out.println(object);// 编译过错  
}

Golang: 对于根本类型来讲,声明即初始化;对于引证类型,声明则初始化为nil。

funccompareVariable(){
varage int
varhashMap *map[string]int
fmt.Println(num) // num = 0
fmt.Println(hashMap) // &hashMap== nil
}

1.2 效果域规矩

Java:对办法、变量及类的可见域规矩是经过 private、protected、public 要害字来控制的,具体如下

Java与Go到底差别在哪,谁要被时代抛弃?

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结构体
typeUser struct{
Name string
Age int
}
// 运用一个结构体
funcmain(){
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 声明实体并运用:

classUser{
privateString name;
privateintage;
publicUser(String name, intage) {
this.name = name;
this.age = age;
}
publicvoidsetName(String name) {
this.name = name;
}
publicvoidsetAge(intage) {
this.age = age;
}
publicString getName() {
returnname;
}
publicintgetAge() {
returnage;
}
publicString print() {
return"{name = "+ name + ",age = "+ age + "}";
}
}
publicclassDemo{
publicstaticvoidmain(String[] args) {
User user = newUser("xiaohong", 29);
System.out.println("user信息:"+ user.print());
}
}
//履行成果
user信息:{name = xiaohong,age = 29}

1.4.2 函数和办法的差异

在 Java 中: 有的“函数”都是根据“类”这个概念构建的,也便是只需在“类”中才会包括所谓的“函数”,这儿的“函数”被称为“办法”,可见上方声明实体并运用。

在 Golang 中: “函数”和“办法”的最根本差异是:函数不根据结构体而是根据包名调用,办法根据结构体调用。如下实例:

packageentity
import"fmt"
typeUser struct{
Name string
Age int
}
// User结构体/指针可调用的"办法",归于User结构体
func(user *User)Solve(){
fmt.Println(user)
}
// 任何地方都可调用的"函数",不归于任何结构体,可经过entity.Solve调用
funcSolve(user *User){
fmt.Println(user)
}
funcmain() {
userPoint := new(entity.User) // 经过new办法创立结构体指针
entity.Solve(userPoint) // 函数调用
userPoint.Solve() // 办法调用
}

1.5 值类型、引证类型以及指针

Java:在Java中不存在显式的指针操作;8种根本数据类型是值类型,数组和目标归于引证类型。

Golang:而 Golang 中存在显式的指针操作,可是 Golang 的指针不像C那么杂乱,不能进行指针运算。

Java与Go到底差别在哪,谁要被时代抛弃?

一切的根本类型都归于值类型,可是有几个类型比较特别,表现出引证类型的特征,分别是 slice、map、channel、interface 。除赋值以外它们都能够当做引证类型来运用。因而当我们这样做时,能够直接运用变量本身而不必指针。

slice 与数组的差异为是否有固定长度,slice无固定长度,数组有固定长度。值得留意的是,在 Golang 中只需同长度、同类型的数组才可视为“同一类型”,比如 []int 和 [3]int 则会被视为不同的类型,这在参数传递的时候会形成编译过错。

1.5.1 数组比照

在 Java 中当向办法中传递数组时,能够直接经过该传入的数组修正原数组内部值(浅仿制)。

在 Golang 中则有两种情况:在不限制数组长度(为 slice )时也直接改变原数组的值,当限制数组长度时会彻底仿制出一份副本,来进行修正(深仿制)。

Java 的数组实践:

  publicstaticvoidmain(String[] args){
int[] array= {1, 2, 3};
change(array);
System.out.println(Arrays.toString(array)); // -1,2,3
}
privatestaticvoidchange(int[] array){
array[0] = -1;
}

Golang 的数组实践:

// 不限制长度(即slice):
funcmain(){
vararray = []int{1, 2, 3}
change(array)
fmt.Println(array) // [-1 2 3]
}
funcchange(array []int){
array[0] = -1
}
// 限制长度(即数组):
funcmain(){
vararray = [3]int{1, 2, 3}
change(array)
fmt.Println(array) //[1 2 3]
}
funcchange(array [3]int){
array[0] = -1
}

1.5.2目标比照

在 Golang 中:传入函数参数的是原目标的一个全新的 copy (有自己的内存地址); go 目标之间赋值是把目标内存的 内容(字段值等)copy 曩昔,所以才会看到 globalUser 修正前后的地址不变,可是目标的内容变了。

在 Java 中:传入函数参数的是原目标的引证的 copy(指向的是相同的内存地址); Java目标之间的赋值是把目标的引证 copy 曩昔,因为引证指向的地址变了,所以目标的内容也变了。

Golang 的目标实践:

//User 界说User结构体
typeUser struct{
Name string
Age int
}
// 界说一个大局的User
varglobalUser = User {
"xiaoming",
28,
}
// modifyUser 界说一个函数,参数为User结构体“目标”,将大局globalUser指向传递过来的User结构体“目标”
funcmodifyUser(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)
}
funcmain(){
varu 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}
classUser{
privateString name;
privateintage;
publicUser(String name, intage) {
this.name = name;
this.age = age;
}
publicvoidsetName(String name) {
this.name = name;
}
publicvoidsetAge(intage) {
this.age = age;
}
publicString getName() {
returnname;
}
publicintgetAge() {
returnage;
}
publicString print() {
return"{name = "+ name + ",age = "+ age + "}";
}
}
publicclassDemo{
privatestaticUser globalUser = newUser("xiaoming",28);
publicstaticvoidmodifyUser(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());
}
publicstaticvoidmain(String[] args) {
User user = newUser("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结构体
typeUser struct{
Name string
Age int
}
funcmain(){
p1 := User{
Name: "xiaohong",
Age: 21,
}
changePerson(p1)
fmt.Println(p1.Name) // xiaohong
changePersonByPointer(&p1)
fmt.Println(p1.Name) // xiaoming
}
funcchangePersonByPointer(user *User){
user.Name = "xiaoming"
}
funcchangePerson(user User){
user.Name = "xiaoming"
}

Java 的指针:

publicclassDemo{
publicstaticvoidchangePerson(User user) {
user.setName("xiaoming");
}
publicstaticvoidmain(String[] args) {
User user = newUser("xiaohong", 29);
changePerson(user);
System.out.println("user信息:"+ user.getName()); // xiaoming
}
}
classUser{
privateString name;
privateintage;
publicUser(String name, intage) {
this.name = name;
this.age = age;
}
publicvoidsetName(String name) {
this.name = name;
}
publicvoidsetAge(intage) {
this.age = age;
}
publicString getName() {
returnname;
}
publicintgetAge() {
returnage;
}
}

02、面向目标

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

在 Java 中:面向目标言语的封装、承继、多态的特性以及“承继( extends )、完成( implements )”等要害字。

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

假定有这么一个场景:动物( Animal )具有名字( Name )、年纪( Age )的根本特性,现在需求完成一个狗( Dog ),且 Dog 需求具有 Animal 所需的一切特性,并且本身具有犬吠( bark ())的动作。

首先来看看最了解的Java要如何写,很简略,运用抽象类描述 Animal 作为一切动物的超类, Dog extends Animal :

publicabstractclassAnimal{
String name;
intage;
}
publicclassDogextendsAnimal{
publicvoidbark(){
System.out.println(age + "岁的"+ name + "在汪汪叫");
}
}
publicclassDemo{
publicstaticvoidmain(String[] args){
Dog dog = newDog();
dog.name = "小龙";
dog.age = 2;
dog.bark(); // 2岁的小龙在汪汪叫
}
}

在 Golang 中,能够这样经过结构体的组合来完成:

packagemain
import"fmt"
typeAnimal struct{
Name string
Age int
}
typeDog struct{
*Animal
}
func(dog *Dog)Bark(){
fmt.Printf("%d岁的%s在汪汪叫", dog.Age, dog.Name)
}
funcmain(){
dog := &Dog{&Animal{
Name: "小龙",
Age: 2,
}}
dog.Bark() // 2岁的小龙在汪汪叫...
}

2.2 侵入式与非侵入式接口

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

这类接口我们称为侵入式接口。“侵入式”的首要表现在于,完成类需求明确声明自己完成了某个接口。

在 Golang 中:非侵入式接口不需求经过任何要害字声明类型与接口之间的完成联系,只需一个类型完成了接口的一切办法,那么这个类型便是这个接口的完成类型。

Java :办理狗的行为,能够经过以下接口完成。

publicinterfaceDog{
voidBark();
}
publicclassDogImplimplementsDog{
@Override
publicvoidBark(){
System.out.println("汪汪叫");
}
}
publicclassDemo{
publicstaticvoidmain(String[] args){
Dog dog = newDogImpl();
dog.Bark(); // 汪汪叫
}
}

Golang : 假定现在有一个 Factory 接口,该接口中界说了 Produce() 办法及 Consume() 办法, CafeFactory 结构体作为其完成类型。那么能够经过以下代码完成。

packageentity
typeFactory interface{
Produce() bool
Consume() bool
}
typeCarFactory struct{
ProductName string
}
func(c *CarFactory)Produce()bool{
fmt.Printf("CarFactory出产%s成功", c.ProductName)
returntrue
}
func(c *CarFactory)Consume()bool{
fmt.Printf("CarFactory消费%s成功", c.ProductName)
returntrue
}
// --------------
packagemain
funcmain(){
factory := &entity.CarFactory{"Car"}
doProduce(factory)
doConsume(factory)
}
funcdoProduce(factory entity.Factory)bool{
returnfactory.Produce()
}
funcdoConsume(factory entity.Factory)bool{
returnfactory.Consume()
}

Golang 的非侵入式接口长处:

简略、高效、按需完成,具体来说如下:

在 Go 中,类没有承继的概念,只需求知道这个类型完成了哪些办法,每个办法是啥行为。

完成类型的时候,只需求关心自己应该供给哪些办法,不必再纠结接口需求拆得多细才合理。接口由运用方按需界说,而不必事前规划。

削减包的引入,因为多引证一个外部的包,就意味着更多的耦合。接口由运用方按本身需求来界说,运用方无需关心是否有其他模块界说过相似的接口。

Java 的侵入式接口长处:

层次结构明晰,对类型的动作行为有严厉的办理。

03、反常处理

在 Java 中: 经过 try..catch..finally 的办法进行反常处理,有或许呈现反常的代码会被 try 块给包裹起来,在 catch 中捕获相关的反常并进行处理,最后经过finally块来一致履行最后的完毕操作(开释资源)。

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

3.1 Java 的反常处理

publicclassExceptionTest{
publicstaticvoidmain(String[] args) {
FileInputStream fileInputStream = null;
try{
fileInputStream = newFileInputStream("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 {} 去做判别和处理,不能做到一致捕捉和处理,简略遗失。

funcmain(){
value, err := Bark()
iferr != nil{
// 回来了反常,进行处理
log.error("...反常:", err)
returnerr
}
// Bark办法履行正确,继续履行后续代码
Process(value)
}
  • Golang 的defer 、panic 及 recover

defer 是 Golang 过错处理中常用的要害字, pannic 及 recover 是 Golang 中的内置函数,一般与 defer 结合进行过错处理,它们各自的用途为:

defer的效果是延迟履行某段代码,一般用于关闭资源或许履行必须履行的收尾操作,不管是否呈现过错defer代码段都会履行,相似于 Java 中的 finally 代码块的效果;defer 也能够履行函数或许是匿名函数:

deferfunc(){
// 收拾作业
} ()
// 这是传递参数给匿名函数时的写法
varnum := 1
deferfunc(num int){
// 做你杂乱的收拾作业
} (num)

需求留意的是, defer 运用一个栈来保护需求履行的代码,所以 defer 函数所履行的顺序是和 defer 声明的顺序相反的:

deferfmt.Println(a)
deferfmt.Println(b)
deferfmt.Println(c)
履行成果:
c
b
a

panic 的效果是抛出过错,制造系统运转时恐慌。当在一个函数履行进程中调用 panic ()函数时,正常的函数履行流程将当即停止。但函数中之前运用 defer 要害字延迟履行的语句将正常打开履行,之后该函数将回来到调用函数,并导致逐层向上履行 panic 流程,直至所属的 goroutine 中一切正在履行的函数被停止, panic 和 Java 中的 throw 要害字相似:用于抛出过错,阻挠程序履行。

recover 的效果是捕捉 panic 抛出的过错并进行处理,需求联合 defer 来运用,相似于 Java 中的 catch 代码块:

funcmain(){
fmt.Println("main begin")
// 必须要先声明defer,不然不能捕获到panic反常
deferfunc(){
fmt.Println("defer begin")
iferr := recover(); err != nil{
// 这儿的err其实便是panic传入的内容
fmt.Println(err)
}
fmt.Println("defer end")
}()
test()
// test中呈现过错,这儿开端下面代码不会再履行
fmt.Println("main end")
}
functest(){
fmt.Println("test begin")
panic("error")
//这儿开端下面代码不会再履行
fmt.Println("test end")
}
//履行成果
main begin
test begin
deferbegin
error
deferend

注:运用 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 = newThread(task);
t.start();

Java 使用一般运用线程池会集处理任务,以防止线程重复创立收回带来的开支:

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

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

gofunc(){
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 官方库中同步办法的对应联系表

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 的运用:

润饰一个代码块,被润饰的代码块称为同步代码块,效果规模是大括号{}括起来的代码:

publicvoidmethod()
{
synchronized(this) {
// todo some thing
}
}

润饰一个办法,被润饰的办法称为同步办法,其效果规模是整个办法:

publicsynchronizedvoidmethod()
{
// todo some thing
}

修正一个静态办法,效果规模是整个静态办法:

publicsynchronizedstaticvoidmethod(){
// todo some thing
}

修正一个类,效果规模是synchronized后面括号括起来的部分:

classDemoClass{
publicvoidmethod(){
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的运用:润饰要害代码:每次只需一个线程对这个要害变量进行修正,防止多个线程一起这个要害代码进行操作。

funcmain(){
varmutex sync.Mutex
count := 0
fori := 0; i < 100; i++ {
gofunc(){
mutex.Lock() // 加锁
count += 1
mutex.Unlock() // 解锁
}()
}
// 休眠,等候2s
time.Sleep(time.Second * 2)
// 100,没有加锁成果不正确
fmt.Println("count = ", count)
}

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

typecount struct{
lock sync.Mutex
value int
}
// 结构体对应的结构办法
func(receiver *count)countOne(){
receiver.lock.Lock()
deferreceiver.lock.Unlock()
receiver.value++
}
funcmain(){
c := count{
lock: sync.Mutex{},
value: 0,
}
group := sync.WaitGroup{}
fori := 0; i < 10; i++ {
group.Add(1)
gofunc(count2 *count){
defergroup.Done()
fori := 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 会堵塞直至代码履行完毕。

funcmain(){
varonce = sync.Once{}
f := func(){
time.Sleep(10* time.Millisecond)
fmt.Println("do once")
}
gofunc(){
fmt.Println("do once start")
once.Do(f)
fmt.Println("do once finish")
}()
time.Sleep(1* time.Millisecond)
fori := 0; i < 2; i++ {
gofunc(){
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,确保线程安全。

//懒汉式单例类.在第一次调用的时候实例化自己
publicclassSingleton{
privateSingleton(){}
privatestaticSingleton single=null;
//静态工厂办法
publicstaticsynchronizedSingleton getInstance(){
if(single == null) {
single = newSingleton();
}
returnsingle;
}
}

饿汉式单例:饿汉式在类创立的一起就已经创立好一个静态的目标供系统运用,今后不再改变,所以天生是线程安全的。

//饿汉式单例类.在类初始化时,已经自行实例化
publicclassSingleton{
privateSingleton(){}
privatestaticfinalSingleton single = newSingleton();
//静态工厂办法
publicstaticSingleton getInstance(){
returnsingle;
}
}

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 的运转态相对耗费内存较少。

Java与Go到底差别在哪,谁要被时代抛弃?

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、总结

最后,上表为各位总结~

Java与Go到底差别在哪,谁要被时代抛弃?

全体来说,二者各有好坏,各位自取所需。以上是本次共享全部内容,欢迎大家在评论区共享沟通。假如觉得内容有用,欢迎转发~

-End-

原创作者|宋欣东

技能责编|宋欣东

Java与Go到底差别在哪,谁要被时代抛弃?

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

Java与Go到底差别在哪,谁要被时代抛弃?

Java与Go到底差别在哪,谁要被时代抛弃?

Java与Go到底差别在哪,谁要被时代抛弃?

Java与Go到底差别在哪,谁要被时代抛弃?

Java与Go到底差别在哪,谁要被时代抛弃?

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

⬇️⬇️⬇️

阅览原文