go與java併發程式設計對比
一、前言
在Java中多執行緒之間是通過共享記憶體進行通訊的,在go中多執行緒之間通訊是基於訊息的,go中的通道是go中多執行緒通訊的基石。
在java中建立的執行緒是與OS執行緒一一對應的,而在go中多個協程(goroutine)對應一個邏輯處理器,每個邏輯處理器與OS執行緒一一對應。
每個執行緒要執行必須要在就緒狀態情況下獲取cpu,而作業系統是基於時間片輪轉演算法來排程執行緒佔用cpu來執行任務的,每個OS執行緒被分配一個時間片來佔用cpu進行任務的執行。
在java中由於建立的執行緒與os執行緒一一對應,所以java中的每個執行緒佔用一個時間片來執行。而go中多個協程對應一個os 執行緒,也就是多個協程對應了一個時間片,go則使用自己的排程策略(非os的排程策略)來讓多個協程使用一個時間片來併發的執行。也就是go中存在兩級策略,一個是go語言層面的排程多個協程公用一個時間片,一個是os層面的排程多個邏輯處理器輪詢佔用不同的時間片。
二、建立協程
在java中建立一個執行緒:
public static void main(String[] args) {
Thread thread = new Thread(() -> {
//run method dosomthing
});
thread.start();
}
12345678
注: 如上程式碼建立了一執行緒並啟動,在java中存在使用者執行緒與deamon執行緒之分,當不存在使用者執行緒時候,jvm程序就退出了(而不管main函式所線上程是否已經結束).
在go中建立一個協程:大連正規婦科醫院 http://mobile.62671288.com/
func main() {
//開啟一個協程,執行匿名函式裡面的內容
go func(){
//do somthing
fmt.Println("im a go 協程")
}()
//休眠10s
time.Sleep(10 * time.Second)
}
12345678910111213
注意: 如上通過go關鍵字開啟一個協程,執行匿名函式裡面的內容,這裡需要注意main函式所線上程需要休眠以下,以便等開啟的協程執行,這是因為go中只要main函式執行緒退出則程序就退出。
三、同步
在java中我們可以使用Semaphore、CountDownLatch、CyclicBarrier等進行多執行緒之間同步,比如下面例子:
public final static Semaphore SEMAPHORE = new Semaphore(0);
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
try {
//run method dosomthing
System.out.println(Thread.currentThread().getName() + "done");
}finally {
SEMAPHORE.release();
}
},"thread-1");
thread1.start();
Thread thread2 = new Thread(() -> {
try {
//run method dosomthing
System.out.println(Thread.currentThread().getName() + "done");
}finally {
SEMAPHORE.release();
}
},"thread-2");
thread2.start();
System.out.println("wait all sub thread end");
SEMAPHORE.acquire(2);
System.out.println("all sub thread end");
}
1234567891011121314151617181920212223242526272829303132333435
在go中也有類似的工具類:
//建立同步器
var wg sync.WaitGroup
func main() {
//兩個訊號
wg.Add(2)
//開啟一個協程,執行匿名函式裡面的內容
go func() {
//訊號量減去1
defer wg.Done()
//do somthing
fmt.Println("im A go 協程")
}()
//開啟一個協程,執行匿名函式裡面的內容
go func() {
//訊號量減去1
defer wg.Done()
//do somthing
fmt.Println("im B go 協程")
}()
fmt.Println("wait all sub thread end")
wg.Wait()
fmt.Println(" all sub thread end")
}
123456789101112131415161718192021222324252627282930313233
四、通道
go中通道分為有緩衝和無緩衝的,本節我們看如何使用有緩衝通道實現生產消費模型
var wg sync.WaitGroup
func printer(ch chan int) {
for i := range ch {
fmt.Println(i)
}
wg.Done()
}
func main() {
//1為攜程建立等待
wg.Add(1)
//2建立緩衝通道
ch := make(chan int ,10)
//3開啟go協程
go printer(ch)
//4寫入到通道
for i := 1; i < 100; i++ {
ch <- i;
}
//5關閉協程
close(ch)
fmt.Println("wait sub thread over")
//6等待攜程結束
wg.Wait()
fmt.Println("main thread over")
}
123456789101112131415161718192021222324252627282930313233343536
如上程式碼2建立了一個可以緩衝10個int 元素的通道,程式碼3開啟一個執行緒用來從通道里面讀取資料,程式碼4在主執行緒裡面寫入資料到通道,程式碼5關閉通道(關閉後不能再向通道寫入資料,但是可以從中讀取)。
上面例子如果用Java來寫,首先需要建立一個併發安全的佇列,然後開啟一個生產執行緒寫入資料到佇列,開啟一個執行緒從佇列讀取元素。