Tujuan pemrograman bersamaan adalah untuk membuat program berjalan lebih cepat, tetapi menggunakan konkurensi mungkin tidak selalu membuat program berjalan lebih cepat. Keuntungan dari pemrograman bersamaan hanya dapat tercermin ketika jumlah program bersamaan mencapai urutan besarnya. Oleh karena itu, hanya bermakna untuk berbicara tentang pemrograman bersamaan ketika ada konkurensi yang tinggi. Meskipun belum ada program dengan volume konkurensi yang tinggi yang telah dikembangkan, pembelajaran konkurensi adalah untuk lebih memahami beberapa arsitektur terdistribusi. Kemudian ketika volume konkurensi program tidak tinggi, seperti program utamanya, efisiensi eksekusi satu threaded lebih tinggi daripada program multi-utas. Mengapa ini? Mereka yang terbiasa dengan sistem operasi harus tahu bahwa CPU mengimplementasikan multi-threading dengan mengalokasikan irisan waktu untuk setiap utas. Dengan cara ini, ketika CPU beralih dari satu tugas ke tugas lain, keadaan tugas sebelumnya akan disimpan. Ketika tugas dieksekusi, CPU akan terus melaksanakan keadaan tugas sebelumnya. Proses ini disebut switching konteks.
Dalam java multithreading, kata kunci yang disinkronkan dari kata kunci yang mudah menguap memainkan peran penting. Mereka semua dapat mengimplementasikan sinkronisasi utas, tetapi bagaimana ia diimplementasikan di bagian bawah?
tidak stabil
Volatile hanya dapat memastikan visibilitas variabel untuk setiap utas, tetapi tidak dapat menjamin atomisitas. Saya tidak akan banyak bicara tentang cara menggunakan bahasa Java yang mudah menguap. Saran saya adalah menggunakannya dalam situasi lain kecuali untuk perpustakaan kelas dalam paket java.util.concurrent.atomic. Lihat artikel ini untuk penjelasan lebih lanjut.
Perkenalan
Lihat kode berikut
paket org.go; kelas publik go {volatile int i = 0; private void inc () {i ++; } public static void main (string [] args) {go go = new go (); untuk (int i = 0; i <10; i ++) {thread baru (() -> {for (int j = 0; j <1000; j ++) go.inc ();}). start (); } while (thread.activeCount ()> 1) {thread.yield (); } System.out.println (go.i); }} Hasil dari setiap eksekusi kode di atas berbeda, dan nomor output selalu kurang dari 10000. Ini karena saat melakukan inc (), i ++ bukan operasi atom. Mungkin beberapa orang akan menyarankan menggunakan sinkronisasi untuk menyinkronkan Inc (), atau menggunakan kunci di bawah paket java.util.concurrent.locks untuk mengontrol sinkronisasi utas. Tetapi mereka tidak sebagus solusi berikut:
paket org.go; import java.util.concurrent.atomic.atomicinteger; kelas publik go {atomicinteger i = atomicinteger baru (0); private void inc () {i.getAndIncrement (); } public static void main (string [] args) {go go = new go (); untuk (int i = 0; i <10; i ++) {thread baru (() -> {for (int j = 0; j <1000; j ++) go.inc ();}). start (); } while (thread.activeCount ()> 1) {thread.yield (); } System.out.println (go.i); }} Pada saat ini, jika Anda tidak memahami implementasi atom, Anda pasti akan curiga bahwa atomicinteger yang mendasarinya dapat diimplementasikan menggunakan kunci, jadi mungkin tidak efisien. Jadi apa sebenarnya itu, mari kita lihat.
Implementasi internal kelas atom
Apakah itu atomicinteger atau concurrentlinkedqueue class concurrentlinkedqueue.node, mereka memiliki variabel statis
Private static final sun.misc.unsafe tidak aman;, kelas ini adalah enkapsulasi Java Sun :: Misc :: Uncafe yang mengimplementasikan semantik atom. Saya ingin melihat implementasi yang mendasarinya. Saya kebetulan memiliki kode sumber GCC4.8. Dibandingkan dengan jalur lokal, sangat nyaman untuk menemukan jalan menuju GitHub. Lihat disini.
Ambil contoh implementasi antarmuka getAndincrement ()
Atomicinteger.java
private static final tidak aman tidak aman = uncafe.getunsafe (); public final int getAndIncrement () {for (;;) {int current = get (); int next = arus + 1; if (compareEndset (saat ini, selanjutnya)) Mengembalikan arus; }} public final boolean compareEndset (int happeC, int update) {return unsafe.comppareandswapint (this, valueOffset, harapkan, perbarui); } Perhatikan ini untuk loop, itu hanya akan kembali jika CompareEndset berhasil. Kalau tidak, itu akan selalu CompareAndset.
Implementasi CompareANDSet dipanggil. Di sini, saya perhatikan bahwa implementasi Oracle JDK sedikit berbeda. Jika Anda melihat SRC di bawah JDK, Anda dapat melihat bahwa Oracle JDK menyebut GetAndINCREMENT (), tetapi saya percaya bahwa ketika Oracle JDK mengimplementasikan tidak aman.java, itu seharusnya hanya memanggil CompareEndset, karena Compareandet dapat menerapkan operasi atom meningkat, menurun, dan penetapan nilai.
Uncafe.java
Public Native Boolean CompareeAndsWapint (Object Obj, Long Offset, Int Harapan, Int Update);
Implementasi C ++ dipanggil melalui JNI.
natunsafe.cc
jbooleansun :: misc :: unsafe :: compareandswapint (jobject obj, jlong offset, jint hare, pembaruan jint) {jint *addr = (jint *) ((char *) obj + offset); return compareandswap (addr, harapkan, perbarui);} static inline boolcompareandswap (volatile jint *addr, jint old, jint new_val) {jboolean hasil = false; kunci spinlock; if ((hasil = ( *addr == lama))) *addr = new_val; Hasil pengembalian;} Tidak aman :: CompareandsWapint menyebut fungsi statis compareandswap. CompareandsWap menggunakan Spinlock sebagai kunci. Spinlock di sini memiliki arti Lockguard, yang terkunci selama konstruksi dan dilepaskan selama kehancuran.
Kita perlu fokus pada Spinlock. Berikut ini untuk memastikan bahwa Spinlock adalah implementasi sebenarnya dari operasi atom sebelum dirilis.
Apa itu Spinlock
Spinlock, semacam sibuk menunggu untuk mendapatkan kunci sumber daya. Tidak seperti Mutex yang memblokir utas saat ini dan melepaskan sumber daya CPU untuk menunggu sumber daya yang diperlukan, Spinlock tidak akan memasuki proses penangguhan, menunggu kondisi dipenuhi, dan kompetisi ulang CPU. Ini berarti bahwa spinlock lebih baik daripada mutex hanya jika biaya menunggu kunci kurang dari biaya sakelar konteks eksekusi utas.
natunsafe.cc
kelas spinlock {static volatile obj_addr_t lock; public: spinlock () {while (! compare_and_swap (& lock, 0, 1)) _jv_threadyield (); } ~ spinlock () {rilis_set (& lock, 0); }}; Gunakan variabel statis statis volatile obj_addr_t lock; Sebagai bit bendera, seorang penjaga diimplementasikan melalui C ++ RAII, sehingga kunci yang disebut sebenarnya adalah variabel anggota statis OBJ_ADDR_T LOCK. Volatile dalam C ++ tidak dapat menjamin sinkronisasi. Yang dijamin adalah perbandingan_and_swap yang dipanggil dalam konstruktor dan kunci variabel statis. Ketika variabel kunci ini adalah 1, Anda harus menunggu; Ketika 0, Anda mengaturnya ke 1 melalui operasi atom, menunjukkan bahwa Anda telah memperoleh kunci.
Ini benar-benar kecelakaan untuk menggunakan variabel statis di sini, yang berarti bahwa semua struktur bebas kunci berbagi variabel yang sama (sebenarnya size_t) untuk membedakan apakah akan menambahkan kunci. Ketika variabel ini diatur ke 1, spinlock lain perlu ditunggu. Mengapa tidak menambahkan variabel pribadi volatile obj_addr_t lock in sun :: misc :: tidak aman dan lewati ke spinlock sebagai parameter konstruktor? Ini setara dengan berbagi bit bendera untuk setiap tidak aman. Akankah efeknya lebih baik?
_Jv_threadyield dalam file berikut, sumber daya CPU diberikan melalui Sistem Panggilan Sejek_YIELD (Man 2 SCHECTER_YIELD). Makro have_sched_yield didefinisikan dalam konfigurasi, yang berarti bahwa jika definisi tidak terdefinisi selama kompilasi, spinlock disebut kunci spin yang benar.
POSIX-Threads.h
inline void_jv_threadyield (void) {#ifdef have_sched_yield sched_yield ();#endif / * have_sched_yield * /} Lock.h ini memiliki implementasi yang berbeda pada platform yang berbeda. Kami mengambil platform IA64 (Intel AMD X64) sebagai contoh. Implementasi lain dapat dilihat di sini.
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 __ ("" ::: "memori"); *(addr) = new_val;}__sync_bool_compare_and_swap adalah fungsi GCC bawaan, dan instruksi perakitan "memori" melengkapi penghalang memori.
Singkatnya, perangkat keras memastikan sinkronisasi CPU multi-core, dan implementasi yang tidak aman seefisien mungkin. GCC-Java cukup efisien, saya percaya Oracle dan OpenJDK tidak akan lebih buruk.
Operasi Atom dan Operasi Atom GCC Built-In
Operasi Atom
Ekspresi Java dan ekspresi C ++ bukan operasi atom, yang berarti Anda berada dalam kode:
// Misalkan saya adalah variabel I ++ yang dibagikan antar utas;
Dalam lingkungan multithreaded, saya akses adalah non-atom dan sebenarnya dibagi menjadi tiga operan berikut:
Kompiler mengubah waktu eksekusi, sehingga hasil eksekusi mungkin tidak diharapkan.
Operasi atom bawaan GCC
GCC memiliki operasi atom bawaan, yang ditambahkan dari 4.1.2. Sebelumnya, mereka diimplementasikan menggunakan perakitan inline.
ketik __sync_fetch_and_add (ketik *ptr, nilai ketik, ...) ketik __sync_fetch_and_sub (ketik *ptr, nilai tipe, ...) __sync_fetch_and_or (ketik *ptr, nilai, ...) ketik __sync_fetch_and_or (type *ptr, nilai jenis, ...) Nilai, ...) Ketik __sync_fetch_and_xor (ketik *ptr, nilai jenis, ...) Ketik __sync_fetch_and_nand (ketik *ptr, nilai jenis, ...) Tipe __sync_add_and_fetch (ketik *ptr, nilai, ...) Tipe __sync_sub_fetch (Tipe *ptr * (Ketik *PTR, Nilai Ketik, ...) Ketik __sync_and_and_fetch (ketik *ptr, nilai tipe, ...) Ketik __sync_and_and_fetch (ketik *ptr, value tipe, ...) Ketik __sync_xor_and_fetch (type *ptr, value type, ...) Tipe __sync_nandandand_nand_fetch (type *ptr, type, ...) Tipe __sync_ncandandander _nc_ncand_nand_nandandeland __sync_bool_compare_and_swap (ketik *ptr, ketik type oldval newVal, ...) ketik __sync_val_compare_and_swap (type *ptr, ketik type oldval newVal, ...) __ sync_synchonize (...) tipe __sync_lock_test_test_test_pet _DSet _IDED (...) __sync_lock_release (ketik *ptr, ...)
Apa yang harus dicatat adalah:
File Terkait OpenJDK
Di bawah ini adalah beberapa implementasi operasi atom OpenJDK9 di GitHub, berharap dapat membantu mereka yang perlu tahu. Bagaimanapun, OpenJDK lebih banyak digunakan daripada GCC. ――― Tapi lagipula, tidak ada kode sumber untuk Oracle JDK, meskipun dikatakan bahwa kode sumber antara OpenJDK dan Oracle sangat kecil.
Atomicinteger.java
Uncafe.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, cmmpxchg_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", "CC"), "R" (Dest), "R" (MP): "CC", "CC"), "R" (DESP), "R" (MP): "CC", "CC"), "R"); return exchange_value;} Di sini kita perlu memberikan prompt kepada programmer Java yang tidak terbiasa dengan C/C ++. Format instruksi perakitan tertanam adalah sebagai berikut
__asm__ [__volatile __] (Templat perakitan // Templat perakitan: [Daftar Operand Output] // Daftar Input: [Daftar Operand Input] // Daftar Output: [Daftar Clobber]) // Daftar Destroy
%1, %3, %4 Dalam templat perakitan sesuai dengan daftar parameter berikut {"r" (Exchange_Value), "r" (dest), "r" (MP)}, dan daftar parameter dipisahkan oleh koma dan diurutkan dari 0. Parameter output ditempatkan di sebelah kanan kolon pertama, dan parameter output ditempatkan di kanan kolon. "R" berarti dimasukkan ke dalam register umum, "A" berarti register eax, dan "=" berarti untuk output (tulis kembali). Instruksi CMPXCHG menyiratkan penggunaan register EAX, yaitu parameter %2.
Detail lainnya tidak akan terdaftar di sini. Implementasi GCC adalah untuk menurunkan pointer untuk ditukar, dan setelah perbandingan yang berhasil, nilainya ditetapkan secara langsung (menetapkan non-atom), dan atomisitas dijamin oleh Spinlock.
Implementasi OpenJDK adalah untuk menurunkan pointer untuk ditukar, dan secara langsung menetapkan nilai melalui instruksi perakitan CMMPXCHGQ, dan atomisitas dijamin melalui instruksi perakitan. Tentu saja, lapisan yang mendasari spinlock GCC juga dijamin melalui CMMPXCHGQ.
Di atas adalah semua konten artikel ini. Saya berharap ini akan membantu untuk pembelajaran semua orang dan saya harap semua orang akan lebih mendukung wulin.com.