亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > Java > 正文

淺談Java 并發的底層實現

2024-07-13 10:13:22
字體:
來源:轉載
供稿:網友

并發編程的目的是讓程序運行更快,但是使用并發并不定會使得程序運行更快,只有當程序的并發數量達到一定的量級的時候才能體現并發編程的優勢。所以談并發編程在高并發量的時候才有意義。雖然目前還沒有開發過高并發量的程序,但是學習并發是為了更好理解一些分布式架構。那么當程序的并發量不高,比如是單線程的程序,單線程的執行效率反而比多線程更高。這又是為什么呢?熟悉操作系統的應該知道,CPU是通過給每個線程分配時間片的方式實現多線程的。這樣,當CPU從一個任務切換到另一個任務的時候,會保存上一個任務的狀態,當執行完這個任務的時候CPU就會繼續上一個任務的狀態繼續執行。這個過程稱為上下文切換。

在Java多線程中,volatile關鍵字個synchronized關鍵字扮演了重要的角色,它們都可以實現線程的同步,但是在底層是如何實現的呢?

volatile

volatile只能保證變量對各個線程的可見性,但不能保證原子性。關于 Java語言 volatile 的使用方法就不多說了,我的建議是 除了 配合package java.util.concurrent.atomic 中的類庫,其他情況一概別用。更多的解釋 參見 這篇文章。

引子

參見如下代碼

 package org.go;public class Go {  volatile int i = 0;  private void inc() {    i++;  }  public static void main(String[] args) {    Go go = new Go();    for (int i = 0; i < 10; i++) {      new Thread(() -> {        for (int j = 0; j < 1000; j++)          go.inc();      }).start();    }    while(Thread.activeCount()>1){      Thread.yield();    }    System.out.println(go.i);  }} 

每次執行上述代碼結果都不同,輸出的數字總是小于10000.這是因為在進行inc()的時候,i++并不是原子操作。或許有些人會提議說用 synchronized 來同步inc() , 或者 用 package java.util.concurrent.locks 下的鎖去控制線程同步。但它們都不如下面的解決方案:

 package org.go;import java.util.concurrent.atomic.AtomicInteger;public class Go {  AtomicInteger i = new AtomicInteger(0);  private void inc() {    i.getAndIncrement();  }  public static void main(String[] args) {    Go go = new Go();    for (int i = 0; i < 10; i++) {      new Thread(() -> {        for (int j = 0; j < 1000; j++)          go.inc();      }).start();    }    while(Thread.activeCount()>1){      Thread.yield();    }    System.out.println(go.i);  }}

這時,如果你不了解 atomic 的實現,你一定會不屑的懷疑 說不定 AtomicInteger 底層就是使用鎖來實現的所以也未必高效。那么究竟是什么,我們來看看。

原子類的內部實現

無論是AtomicInteger 或者是 ConcurrentLinkedQueue的節點類ConcurrentLinkedQueue.Node,他們都有個靜態變量
 private static final sun.misc.Unsafe UNSAFE;,這個類是實現原子語義的C++對象sun::misc::Unsafe的Java封裝。想看看底層實現,正好我手邊有gcc4.8的源代碼,對照本地路徑,很方便找到Github的路徑,看這里。

以接口 getAndIncrement()的實現舉例

AtomicInteger.java

 private static final Unsafe unsafe = Unsafe.getUnsafe();public final int getAndIncrement() {    for (;;) {      int current = get();      int next = current + 1;      if (compareAndSet(current, next))        return current;    }  }public final boolean compareAndSet(int expect, int update) {      return unsafe.compareAndSwapInt(this, valueOffset, expect, update);    } 

留意這個for循環,只有在compareAndSet成功時才會返回。否則就一直compareAndSet。

調用了compareAndSet實現。此處,我注意到 Oracle JDK的實現是略有不同的,如果你查看JDK下的src,你可以看到Oracle JDK是調用的Unsafe的getAndIncrement(),但我相信Oracle JDK實現Unsafe.java的時候應該也是只調用compareAndSet,因為一個compareAndSet就可以實現增加、減少、設值的原子操作了。

Unsafe.java

 public native boolean compareAndSwapInt(Object obj, long offset,                     int expect, int update); 

通過JNI調用的C++的實現。

natUnsafe.cc

 jbooleansun::misc::Unsafe::compareAndSwapInt (jobject obj, jlong offset,           jint expect, jint update){ jint *addr = (jint *)((char *)obj + offset); return compareAndSwap (addr, expect, update);}static inline boolcompareAndSwap (volatile jint *addr, jint old, jint new_val){ jboolean result = false; spinlock lock; if ((result = (*addr == old)))  *addr = new_val; return result;} 

Unsafe::compareAndSwapInt調用 static 函數 compareAndSwap。而compareAndSwap又使用spinlock作為鎖。這里的spinlock有LockGuard的意味,構造時加鎖,析構時釋放。

我們需要聚焦在spinlock里。這里是保證spinlock釋放之前都是原子操作的真正實現。

什么是spinlock

spinlock,即自旋鎖,一種循環等待(busy waiting)以獲取資源的鎖。不同于mutex的阻塞當前線程、釋放CPU資源以等待需求的資源,spinlock不會進入掛起、等待條件滿足、重新競爭CPU的過程。這意味著只有在 等待鎖的代價小于線程執行上下文切換的代價時,Spinlock才優于mutex。

natUnsafe.cc

 class spinlock{ static volatile obj_addr_t lock;public:spinlock () {  while (! compare_and_swap (&lock, 0, 1))   _Jv_ThreadYield (); } ~spinlock () {  release_set (&lock, 0); }}; 

以一個靜態變量 static volatile obj_addr_t lock; 作為標志位,通過C++ RAII實現一個Guard,所以所謂的鎖其實是 靜態成員變量obj_addr_t lock,C++中volatile 并不能保證同步,保證同步的是構造函數里調用的 compare_and_swap和一個static變量lock.這個lock變量是1的時候,就需要等;是0的時候,就通過原子操作把它置為1,表示自己獲得了鎖。

這里會用一個static變量實在是一個意外,如此相當于所有的無鎖結構都共用同一個變量(實際就是size_t)來區分是否加鎖。當這個變量置為1時,其他用到spinlock的都需要等。 為什么不在sun::misc::Unsafe添加一個私有變量 volatile obj_addr_t lock;,并作為構造參數傳給spinlock?這樣相當于每個UnSafe共享一個標志位,效果會不會好一些?

_Jv_ThreadYield在下面的文件里,通過系統調用sched_yield(man 2 sched_yield)讓出CPU資源。宏HAVE_SCHED_YIELD在configure里定義,意味著編譯時如果取消定義,spinlock就稱為真正意義的自旋鎖了。

posix-threads.h

 inline void_Jv_ThreadYield (void){#ifdef HAVE_SCHED_YIELD sched_yield ();#endif /* HAVE_SCHED_YIELD */}

這個lock.h在不同平臺有著不同的實現,我們以ia64(Intel AMD x64)平臺舉例,其他的實現可以在 這里 看到。

ia64/locks.h
 

typedef size_t obj_addr_t;inline static boolcompare_and_swap(volatile obj_addr_t *addr,             obj_addr_t old,             obj_addr_t new_val){ return __sync_bool_compare_and_swap (addr, old, new_val);}inline static voidrelease_set(volatile obj_addr_t *addr, obj_addr_t new_val){ __asm__ __volatile__("" : : : "memory"); *(addr) = new_val;} 

__sync_bool_compare_and_swap 是gcc內建函數,匯編指令"memory"完成內存屏障。

  1. 一般地,如果CPU硬件支持指令 cmpxchg (該指令從硬件保障原子性,毫無疑問十分高效),那么__sync_bool_compare_and_swap就應該是用cmpxchg來實現的。
  2. 不支持cmpxchg的CPU架構 可以用lock指令前綴,通過鎖CPU總線的方式實現。
  3. 如果連lock指令都不支持,有可能通過APIC實現

總之,硬件上保證多核CPU同步,而Unsafe的實現也是盡可能的高效。GCC-java的還算高效,相信Oracle 和 OpenJDK不會更差。

原子操作 和 GCC內建的原子操作

原子操作

Java的表達式以及C++的表達式,都不是原子操作,也就是說 你在代碼里:

 //假設i是線程間共享的變量i++; 

在多線程環境下,i的訪問是非原子性的,實際分成如下三個操作數:

  1. 從緩存取到寄存器
  2. 在寄存器加1
  3. 存入緩存

編譯器會改變執行的時序,因此執行結果可能并非所期望的。

GCC內建的原子操作

gcc內建了如下的原子操作,這些原子操作從4.1.2被加入。而之前,他們是使用內聯的匯編實現的。

 type __sync_fetch_and_add (type *ptr, type value, ...)type __sync_fetch_and_sub (type *ptr, type value, ...)type __sync_fetch_and_or (type *ptr, type value, ...)type __sync_fetch_and_and (type *ptr, type value, ...)type __sync_fetch_and_xor (type *ptr, type value, ...)type __sync_fetch_and_nand (type *ptr, type value, ...)type __sync_add_and_fetch (type *ptr, type value, ...)type __sync_sub_and_fetch (type *ptr, type value, ...)type __sync_or_and_fetch (type *ptr, type value, ...)type __sync_and_and_fetch (type *ptr, type value, ...)type __sync_xor_and_fetch (type *ptr, type value, ...)type __sync_nand_and_fetch (type *ptr, type value, ...)bool __sync_bool_compare_and_swap (type *ptr, type oldval type newval, ...)type __sync_val_compare_and_swap (type *ptr, type oldval type newval, ...)__sync_synchronize (...)type __sync_lock_test_and_set (type *ptr, type value, ...)void __sync_lock_release (type *ptr, ...) 

需要注意的是:

  1.  __sync_fetch_and_add 和 __sync_add_and_fetch 的關系 對應于 i++ 和 ++i。其他類推
  2.  CAS的兩種實現,bool版本的 如果對比oldval與ptr成功并給ptr設值newval 返回true;另一個 返回 原本*ptr的值
  3.  __sync_synchronize 添加一個完全的內存屏障

OpenJDK 的相關文件

下面列出一些Github上 OpenJDK9的原子操作實現,希望能幫助需要了解的人。畢竟OpenJDK比Gcc的實現應用更廣泛一些。————但終究沒有Oracle JDK的源碼,雖然據說OpenJDK與 Oracle的源碼差距很小。

AtomicInteger.java

Unsafe.java::compareAndExchangeObject

unsafe.cpp::Unsafe_CompareAndExchangeObject

oop.inline.hpp::oopDesc::atomic_compare_exchange_oop

atomic_linux_x86.hpp::Atomic::cmpxchg

 inline jlong  Atomic::cmpxchg  (jlong  exchange_value, volatile jlong*  dest, jlong  compare_value, cmpxchg_memory_order order) { bool mp = os::is_MP(); __asm__ __volatile__ (LOCK_IF_MP(%4) "cmpxchgq %1,(%3)"            : "=a" (exchange_value)            : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)            : "cc", "memory"); return exchange_value;} 

這里需要給不熟悉C/C++的Java程序員提示一下,嵌入匯編指令的格式如下

 __asm__ [__volatile__](assembly template//匯編模板    : [output operand list]//輸入列表    : [input operand list]//輸出列表    : [clobber list])//破壞列表 

匯編模板中的%1,%3,%4對應于后面的參數列表{"r" (exchange_value),"r" (dest),"r" (mp)},參數列表以逗號分隔,從0排序。輸出參數放第一個冒號右邊,輸出參數放第二個冒號右邊。"r"表示放到通用寄存器,"a"表示寄存器EAX,有"="表示用于輸出(寫還)。cmpxchg指令隱含使用EAX寄存器即參數%2.

其他細節就不在此羅列了,Gcc的實現是把要交換的指針傳下來,對比成功后直接賦值(賦值非原子),原子性通過spinlock保證。 

OpenJDK的實現是把要交換的指針傳下來,直接通過匯編指令cmpxchgq賦值,原子性通過匯編指令保證。當然gcc的spinlock底層也是通過cmpxchgq保證的。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持VeVb武林網。


注:相關教程知識閱讀請移步到JAVA教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美丰满少妇xxxxx做受| 日韩中文字幕免费| 欧美性一区二区三区| 欧洲美女7788成人免费视频| 欧美成人第一页| 亚洲欧美国产高清va在线播| 91福利视频在线观看| 国产精品吊钟奶在线| 国产亚洲欧美aaaa| 亚洲欧美综合v| 欧美大成色www永久网站婷| 欧美一区亚洲一区| 国产成人精品视| 亚洲开心激情网| 亚洲va欧美va国产综合久久| 久久九九精品99国产精品| 亚洲美女av在线| 91精品国产综合久久香蕉最新版| 91高清视频免费| 久久久精品免费视频| 欧美视频中文在线看| 亚洲一区二区福利| 日韩av一卡二卡| 成人免费观看49www在线观看| 亚洲精品色婷婷福利天堂| 国产v综合ⅴ日韩v欧美大片| 黑人巨大精品欧美一区二区一视频| 久久久久久免费精品| 日韩在线视频国产| 欧美成人午夜剧场免费观看| 不卡av在线播放| 精品小视频在线| 北条麻妃在线一区二区| 国产亚洲精品美女久久久久| 欧美韩日一区二区| 国产精品香蕉av| 成人久久18免费网站图片| 亚洲免费高清视频| 亚洲色在线视频| 成人黄色av网站| 亚洲人成电影网站色…| 国产精品成人一区二区三区吃奶| 日韩成人av网址| 91精品视频播放| 色综合天天综合网国产成人网| 久久久国产一区二区三区| 久久久午夜视频| 91在线观看免费| 狠狠色狠狠色综合日日五| 亚洲国产私拍精品国模在线观看| 亚洲精品在线看| 欧美激情高清视频| 色播久久人人爽人人爽人人片视av| 日韩的一区二区| 日韩欧美亚洲一二三区| 欧美久久精品午夜青青大伊人| 国产一区视频在线播放| 久久夜色精品国产亚洲aⅴ| 欧美激情精品在线| 成人国产精品久久久久久亚洲| 久久成人精品电影| 久久久91精品国产| 亚洲一区av在线播放| 中文日韩在线视频| 久久精品色欧美aⅴ一区二区| 在线激情影院一区| 久久成年人视频| 国产精品精品一区二区三区午夜版| 一区二区三区四区精品| 久久久久久香蕉网| 日韩在线观看成人| 亚洲欧美综合另类中字| 国内精品国产三级国产在线专| 日韩成人中文电影| 一本色道久久88亚洲综合88| 久久天天躁狠狠躁夜夜躁2014| 欧美日韩亚洲网| 国产亚洲精品久久久| 久久久久久国产精品三级玉女聊斋| 欧美国产中文字幕| 国产欧美日韩专区发布| 日韩av电影手机在线观看| 欧美亚洲激情视频| 俺去了亚洲欧美日韩| 欧美激情综合亚洲一二区| 91精品国产91久久久| 精品国产成人在线| 色中色综合影院手机版在线观看| 国产一区二区三区在线观看网站| 91欧美激情另类亚洲| 亚洲精品动漫100p| 国产精品美女网站| 久久天天躁狠狠躁夜夜躁2014| 丁香五六月婷婷久久激情| 中文字幕日本欧美| 亚洲伊人成综合成人网| 亚洲一区二区三区四区视频| 精品调教chinesegay| 欧美国产中文字幕| 成人羞羞国产免费| 91麻豆国产精品| 尤物tv国产一区| 欧美日韩中文字幕在线视频| 亚洲福利影片在线| 精品亚洲va在线va天堂资源站| 日产精品久久久一区二区福利| 亚洲国产中文字幕在线观看| 日韩美女视频免费看| 97超级碰在线看视频免费在线看| 成人免费视频xnxx.com| 中文字幕欧美精品在线| 精品欧美激情精品一区| 国产精品色婷婷视频| 亚洲国产精彩中文乱码av| 亚洲欧美精品在线| 日韩日本欧美亚洲| 亚洲日韩欧美视频一区| 欧美巨乳在线观看| 久久久爽爽爽美女图片| 久久青草精品视频免费观看| 日韩在线观看高清| 日本精品免费一区二区三区| 亚洲精品白浆高清久久久久久| 自拍偷拍亚洲区| 4p变态网欧美系列| 亚洲a区在线视频| 欧美性一区二区三区| 国产在线观看一区二区三区| 黄网动漫久久久| 91亚洲人电影| 国产一区二区三区欧美| 欧美午夜丰满在线18影院| 欧美中文字幕在线播放| 国产一区二区三区中文| 中日韩美女免费视频网址在线观看| 国产视频精品va久久久久久| 97在线免费观看视频| 麻豆国产精品va在线观看不卡| 欧美怡春院一区二区三区| 亚洲加勒比久久88色综合| 欧美性受xxxx白人性爽| 2018国产精品视频| 91久久国产婷婷一区二区| 国产经典一区二区| 国产亚洲激情视频在线| 日韩成人中文电影| 国产精品一区二区久久| 日韩欧美在线免费观看| 欧美午夜精品久久久久久人妖| 不卡av电影在线观看| 久久久久国产一区二区三区| 亚洲成人网在线观看| 狠狠色狠色综合曰曰| 久久琪琪电影院| 欧美日韩国产综合视频在线观看中文| 国产精品一二三在线| 国产欧美精品在线| 国产精品一区二区女厕厕| xvideos成人免费中文版| 欧美精品第一页在线播放| 久久久久日韩精品久久久男男| 成人激情在线观看| 亚洲精选一区二区| 中文字幕在线看视频国产欧美在线看完整|