主頁 > 後端開發 > Golang與Java全方位對比總結

Golang與Java全方位對比總結

2023-04-10 07:24:07 後端開發

本文針對Golang與Java的基礎語法、結構體函式、例外處理、并發編程及垃圾回收、資源消耗等各方面的差異進行對比總結,有不準確、不到位的地方還請大家不吝賜教,

一 、基礎語法

Golang: 編碼風格及可見域規則嚴格且簡單;Java: 來說層次介面清晰、規范,主要表現有以下這些,

1、變數

a、變數宣告及使用

在Java中:變數可以宣告了卻不使用

public static String toString(int num) { int data = https://www.cnblogs.com/88223100/archive/2023/04/09/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)
}
b、變數宣告及初始化

在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
}

2、作用域規則

Java: 對方法、變數及類的可見域規則是通過private、protected、public關鍵字來控制的,具體如下

作用域 當前類 同一package 子孫類 其他package
public
protected ×
default(無修飾詞) × ×
private × × ×

Golang: 控制可見域的方式只有一個,當欄位首字母開頭是大寫時說明其是對外可見的、小寫時只對包內成員可見,

3、逗號 ok 模式

在使用Golang撰寫代碼的程序中,許多方法經常在一個運算式回傳2個引數時使用這種模式:,ok,第一個引數是一個值或者nil,第二個引數是true/false或者一個錯誤error,在一個需要賦值的if條件陳述句中,使用這種模式去檢測第二個引數值會讓代碼顯得優雅簡潔,這種模式在Golang編碼規范中非常重要,這是Golang自身的函式多回傳值特性的體現,例如:

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

4、結構體、函式以及方法

a、結構體宣告及使用

在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}
b、函式和方法的區別

在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()    // 方法呼叫
}

5、值類型、參考型別以及****指標

Java:在Java中不存在顯式的指標操作;8種基本資料型別是值型別,陣列和物件屬于參考型別,

Golang:而Golang中存在顯式的指標操作,但是Golang的指標不像C那么復雜,不能進行指標運算;所有的基本型別都屬于值型別,但是有幾個型別比較特殊,表現出參考型別的特征,分別是slice、map、channel、interface,除賦值以外它們都可以當做參考型別來使用,因此當我們這樣做時,可以直接使用變數本身而不用指標,

注:slice與陣列的區別為是否有固定長度,slice無固定長度,陣列有固定長度,值得注意的是,在Golang中,只有同長度、同型別的陣列才可視為“同一型別”,譬如[]int和[3]int則會被視為不同的型別,這在引數傳遞的時候會造成編譯錯誤,

a、陣列對比

在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
} 
b、物件對比

在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}
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 {
    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}

c、指標的區別

在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;
    }
}

二、面向物件

在Golang中:沒有明確的OOP概念,Go語言只提供了兩個關鍵型別:struct,interface, 在Java中: 面向物件語言的封裝、繼承、多型的特性以及“繼承(extends)、實作(implements)”等關鍵字,

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、侵入式與非侵入式介面

在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的侵入式介面優點: 層次結構清晰,對型別的動作行為有嚴格的管理

三、例外處理

在Java中: 通過try..catch..finally的方式進行例外處理,有可能出現例外的代碼會被try塊給包裹起來,在catch中捕獲相關的例外并進行處理,最后通過finally塊來統一執行最后的結束操作(釋放資源),

在Golang中:錯誤處理方式有兩種方式:**,ok模式** 與 defer、panic及recover的組合

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("回收資源");
        }
    }
}

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,

 

四、并發編程

Java 中 CPU 資源分配物件是 Thread,Go 中 CPU 資源分配物件是 goroutine,Java Thread 與系統執行緒為一一對應關系,goroutine 是 Go 實作的用戶級執行緒,與系統執行緒是 m:n 關系,

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")
}()

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 Golang
synchronized,ReentrantLock sync.Mutex, one unit buffered channel
讀寫鎖 ReentrantReadWriteLock, StampedLock sync.RWMutex
條件變數 condition sync.Cond
CAS/Atomic Varhandle、volatile,Atomic 類 atomic.Value,atomic 包
once 單例模式 sync.Once
a、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
}
b、條件變數

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() 之后執行,

c、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 包,

d、Once 與單例模式

sync.Once 是 Golang 標準庫提供的使函式只執行一次的實作,常應用于單例模式,例如初始化配置、保持資料庫連接等,它有 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;
    }
}

五、垃圾回收

GC(Garbage Collection)垃圾回收是一種自動管理記憶體的方式,支持GC的語言無需手動管理記憶體,程式后臺自動判斷物件是否存活并回收其記憶體空間,使開發人員從記憶體管理上解脫出來, 因為支持更多的特性和更靈活多樣的GC策略, 比如分代,物件可移動,各種引數調節等等. 而Go只做了一種GC方案,不分代,不可移動,沒什么引數能調節,而且更注重暫停時間的優化,執行GC的時機更頻繁, 所以Go通常更占更少的記憶體,但代價就是GC性能比JVM差了不少,

1、Java的垃圾回收體系

Java基于JVM完成了垃圾收集的功能,其體系很龐大,包括了垃圾回收器(G1、CMS、Serial、ParNew等)、垃圾回收演算法(標記-清除、標記-整理、復制、分代收集)、可達性演算法(可達性分析、參考計數法)、參考型別、JVM記憶體模型等內容,經過多代發展,Java的垃圾回識訓制較為完善,Java劃分新生代、老年代來存盤物件,物件通常會在新生代分配記憶體,多次存活的物件會被移到老年代,由于新生代存活率低,產生空間碎片的可能性高,通常選用“標記-復制”作為回收演算法,而老年代存活率高,通常選用“標記-清除”或“標記-整理”作為回收演算法,壓縮整理空間,

2、Golang GC特征

三色標記、并發標記和清掃、非分代、非緊縮、寫屏障

a、三色標記

a、程式開始時有黑白灰三個集合,初始時所有物件都是白色; b、從root物件開始標記,將所有可達物件標記為灰色; c、從灰色物件集合取出物件,將其參考物件標記為灰色,放入灰色集合,并將自己標記為黑色; d、重復第三步,直到灰色集合為空,即所有可達物件全部都被標記; e、標記結束后,不可達白色物件即為垃圾,對記憶體進行迭代清掃,回收白色物件; f、重置GC狀態;

b、非分代

Java采用分代回收(按照物件生命周期長短劃分不同的代空間,生命周期長的放入老年代,短的放入新生代,不同代有不同的回收演算法和回收頻率),Golang沒有分代,一視同仁;

c、非緊縮

在垃圾回收之后不會進行記憶體整理以清除記憶體碎片;

d、寫屏障

在并發標記的程序中,如果應用程式修改了物件圖,就可能出現標記遺漏的可能,寫屏障是為了處理標記遺漏的問題,

 

六、資源消耗對比

在記憶體利用效率上,Go語言確實比Java做得更好,在4個不同的角度來總結:

1、Java的JIT策略比Golang的AOT策略

Java在運行時相比Golang多占用了一些記憶體,原因在于:

Java運行態中包含了一個完整的解釋器、一個JIT編譯期以及一個垃圾回收器,這會顯著地增加記憶體,

Golang語言直接編譯到機器碼,運行態只包含機器碼和一個垃圾回收器,

因此Golang的運行態相對消耗記憶體較少,

圖片

2、記憶體分配和垃圾回收器

Java確實在起步占用上偏多,畢竟jvm需要更多記憶體做jit,默認的gc演算法對記憶體要求偏高,但這不能代表后續占用仍然線性增長,如果目標是啟動成百上千個記憶體需求較少的行程,那Java確實不擅長,

3、并發

協程模型比執行緒模型更加節省記憶體,

4、反射

Golang的反射更加簡單,導致框架的記憶體消耗Golang程式比Java程式優秀,主要是因為: Java的框架實作中大量使用反射,并使用hashmap快取資訊,這2個都是極度消耗記憶體的行為, Golang的框架中也使用reflect、map,但是Golang是面向interface和值型別的,這導致Golang的反射模型要比Java的反射模型簡單非常多,反射程序要產生的物件數量也少非常多,

 

七、生態

Java 在生態這方面簡直是無敵的存在,這主要得益于 Spring 全家桶,Spring 讓 Java 走上了神座,Golang 語言知名的框架也很多,但是遠遠沒有 Spring 影響那么大,

 

總結

  優點 缺點
Golang 代碼簡潔性 靜態型別可編譯成機器碼直接運行 天生多核并行 垃圾收集 跨平臺且不依賴運行時環境 簡潔的泛型 有限的庫支持 泛型不夠完善 靈活度沒Java高(這個可算優點也可算缺點)
java 優秀的生態 優秀的三方庫 多執行緒 靈活性高 平臺獨立性 完善的語言特性 代碼結構層次清晰 大量冗余的陳舊實作導致性能不佳 生態的復雜性 復雜的繼承機制

 

作者:xindong

本文來自博客園,作者:古道輕風,轉載請注明原文鏈接:https://www.cnblogs.com/88223100/p/Summary-of-Comprehensive-Comparison-between-Golang-and-Java.html

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/549553.html

標籤:其他

上一篇:MordernC++之左值(參考)與右值(參考)

下一篇:Go 語言切片是如何擴容的?

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more