


Soalan temu bual sebenar: Sila bincangkan tentang mekanisme CAS secara serentak
Saya tertanya-tanya adakah pelajar pernah mengalami temu duga sebegini:
Penemuduga: Sila bincang tentang mekanisme CAS secara serentak
Xiao Ming: Baiklah, CAS, kan? Fikirkanlah (otak berfikir dengan pantas)2 minit telah berlalu...
Udara sunyi sepi...Penemuduga tidak boleh duduk diam dan berdehem: Ehem... Itu, Boleh awak beritahu saya secara ringkas?
Xiao Ming tersenyum naif: Hehe, saya macam terlupa...
Penemuduga: Oh, tak apa, itu sahaja untuk temuduga hari ini, awak balik dan tunggu pemberitahuan
Xiao Ming pergi dengan sedih...
don't Laugh, Xiao Ming sebenarnya bayang -bayang banyak orang. Foundation tak kukuh!
Maka persoalannya, bagaimana untuk mengalahkan penemuduga dalam temuduga dan menjadi stabil seperti batu?
Belajar! Apa gunanya hanya bercakap? Anda perlu belajar, anda perlu membaca buku yang anda beli, dan anda perlu mengikuti kursus yang anda beli. Jangan hanya bermain dan mengikuti drama TV kena botak! Sekarang 0:08 waktu Beijing Saya sedang menulis artikel dalam kod, bagaimana dengan anda?
Sebuah contoh kecil untuk bercakap tentang apa itu keselamatan benang
Concurrency adalah asas pengaturcaraan Java Dalam kerja harian kita, kita sering berurusan dengan concurrency. Sudah tentu, ini juga diuji temu bual. Dalam pengaturcaraan serentak, konsep yang paling banyak disebut ialah 线程安全
Mari kita lihat sekeping kod untuk melihat apa yang berlaku selepas dijalankan:
public class Test { private static int inc = 0; public static void main(String[] args) { // 设置栅栏,保证主线程能获取到程序各个线程全部执行完之后的值 CountDownLatch countDownLatch = new CountDownLatch(1000000); // 设置100个线程同时执行 for (int i = 0; i < 100; i++) { new Thread(() -> { // 循环10000次,对inc实现 +1 操作 for (int j = 0; j < 10000; j++) { inc++; countDownLatch.countDown(); } }).start(); } try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } // 运行完毕,期望获取的结果是 1000000 System.out.println("执行完毕,inc的值为:" + inc); } }
Dalam program ini, saya mencipta 100 utas dan berkongsi pembolehubah dalam setiap utasinc
melakukan operasi terkumpul 10,000 kali. Jika ia dilaksanakan secara serentak, nilai akhir inc hendaklah 1,000,000, tetapi kita tahu bahawa dalam berbilang benang, atur cara dilaksanakan serentak, yang bermaksud berbeza Benang mungkin membaca nilai yang sama daripada memori utama pada masa yang sama, seperti senario ini: inc
进行累加10000次的操作,如果是同步执行的话,inc最终的值应该是1000000,但我们知道在多线程中,程序是并发执行的,也就是说不同的线程可能会同时读取到主内存相同的值,比如这样的场景:
线程A在某一个瞬间读取了主内存的inc值为1000,它在自己的工作内存 +1,inc变成了1001; 线程B在同样的瞬间读取到了主内存的inc值为1000,它也在自己的工作内存中对inc的值 +1, inc变成了1001; 他们要往主内存写入inc的值的时候并没有做任何的同步控制,所以他们都有可能把自己工作内存的1001写入到主内存; 那么很显然主内存在进行两次 +1 操作后,实际的结果只进行了一次 +1,变成了1001。
这就是一个很典型的多线程并发修改共享变量带来的问题,那么很显然,它的运行结果也如我们分析的那样,某些情况下达不到1000000:
执行完毕,inc的值为:962370
有些人说通过volatile
关键字可以解决这个问题,因为volatile可以保证线程之间的可见性,也就是说线程可以读取到主内存最新的变量值,然后对其进行操作。
注意了,volatile只能保证线程的可见性
,而不能保证线程操作的原子性
,虽然线程读取到了主内存的inc的最新值,但是 读取
、inc+1
、写入主内存
Benang A membaca nilai inc memori utama sebagai 1000 pada masa tertentu. Ia menambahkan 1 pada memori kerjanya sendiri dan inc menjadi 1001; Thread B membaca nilai inc memori utama sebagai 1000 pada masa yang sama, dan ia juga Tambahkan 1 pada nilai inc dalam memori kerja anda sendiri, dan inc menjadi 1001; : 26px;color: rgb(1, 1, 1);">Apabila mereka ingin menulis nilai inc ke memori utama, mereka tidak melakukan sebarang kawalan penyegerakan, jadi mereka semua boleh menulis 1001 memori kerja mereka ke Utama memori; Kemudian adalah jelas bahawa selepas memori utama melakukan dua operasi +1, hasil sebenar hanya melakukan +1 sekali dan menjadi 1001.
public class Test { private static int inc = 0; public static void main(String[] args) { // 设置栅栏,保证主线程能获取到程序各个线程全部执行完之后的值 CountDownLatch countDownLatch = new CountDownLatch(1000000); // 设置100个线程同时执行 for (int i = 0; i < 100; i++) { new Thread(() -> { // 循环10000次,对inc实现 +1 操作 for (int j = 0; j < 10000; j++) { // 设置同步机制,让inc按照顺序执行 synchronized (Test.class) { inc++; } countDownLatch.countDown(); } }).start(); } try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("执行完毕,inc的值为:" + inc); } }
Keterlihatan </ kod>, dan tidak boleh menjamin operasi benang<code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba (27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">Atomicity
, walaupun urutan membaca nilai terkini inc dalam memori utama, Baca
, inc+1</ code>, <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05 );font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">Write to main memory< /code> is a operasi tiga langkah, jadi tidak menentu tidak dapat menyelesaikan masalah keselamatan benang pembolehubah yang dikongsi. 🎜🎜Jadi bagaimana untuk menyelesaikan masalah ini? Java menyediakan kami dengan penyelesaian berikut. 🎜<h2 id="span-style-display-inline-block-background-rgb-color-rgb-padding-px-px-px-border-top-right-radius-px-border-top-left-radius-px-margin-right-px-几种保证线程安全的方案-span-span-style-display-inline-block-vertical-align-bottom-border-bottom-px-solid-efebe-border-right-px-solid-transparent-span"><span style="display: inline-block;background: rgb(239, 112, 96);color: rgb(255, 255, 255);padding: 3px 10px 1px;border-top-right-radius: 3px;border-top-left-radius: 3px;margin-right: 3px;">几种保证线程安全的方案</span><span style="display: inline-block;vertical-align: bottom;border-bottom: 36px solid #efebe9;border-right: 20px solid transparent;"> </span></h2><h3 id="通过synchronized关键字实现同步">1. 通过synchronized关键字实现同步:</h3><div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class='brush:php;toolbar:false;'>public class Test {
private static int inc = 0;
public static void main(String[] args) {
// 设置栅栏,保证主线程能获取到程序各个线程全部执行完之后的值
CountDownLatch countDownLatch = new CountDownLatch(1000000);
// 设置100个线程同时执行
for (int i = 0; i < 100; i++) {
new Thread(() -> {
// 循环10000次,对inc实现 +1 操作
for (int j = 0; j < 10000; j++) {
// 设置同步机制,让inc按照顺序执行
synchronized (Test.class) {
inc++;
}
countDownLatch.countDown();
}
}).start();
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行完毕,inc的值为:" + inc);
}
}</pre><div class="contentsignin">Salin selepas log masuk</div></div><div class="contentsignin">Salin selepas log masuk</div></div><p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">在上面的代码中,我们给 <code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;color: rgb(239, 112, 96);">inc ++
外面加了一层代码,使用 synchronized
设置类锁
,保证了代码的同步执行,这是一种基于JVM自身的机制来保障线程的安全性,如果在并发量比较大的情况下,synchronized 会升级为重量级的锁,效率很低。synchronized无法获取当前线程的锁状态,发生异常的情况下会自动解锁,但是如果线程发生阻塞,它是不会释放锁的执行结果:
执行完毕,inc的值为:1000000
可以看到,这种方式是可以保证线程安全的。
2. 通过Lock锁实现同步
public class Test { private static int inc = 0; private static Lock lock = new ReentrantLock(); public static void main(String[] args) { // 设置栅栏,保证主线程能获取到程序各个线程全部执行完之后的值 CountDownLatch countDownLatch = new CountDownLatch(1000000); // 设置100个线程同时执行 for (int i = 0; i < 100; i++) { new Thread(() -> { // 循环10000次,对inc实现 +1 操作 for (int j = 0; j < 10000; j++) { // 设置锁 lock.lock(); try { inc++; } finally { // 解锁 lock.unlock(); } countDownLatch.countDown(); } }).start(); } try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("执行完毕,inc的值为:" + inc); } }
ReentrantLock
的底层是通过AQS + CAS
来实现的,在并发量比较小的情况下,它的性能不如 synchronized
,但是随着并发量的增大,它的性能会越来越好,达到一定量级会完全碾压synchronized
。并且Lock是可以尝试获取锁的,它通过代码手动去控制解锁,这点需要格外注意。
执行结果:
执行完毕,inc的值为:1000000
3. 使用 Atomic 原子类
public class Test { private static AtomicInteger inc = new AtomicInteger(); public static void main(String[] args) { // 设置栅栏,保证主线程能获取到程序各个线程全部执行完之后的值 CountDownLatch countDownLatch = new CountDownLatch(1000000); // 设置100个线程同时执行 for (int i = 0; i < 100; i++) { new Thread(() -> { // 循环10000次,对inc实现 +1 操作 for (int j = 0; j < 10000; j++) { inc.getAndAdd(1); countDownLatch.countDown(); } }).start(); } try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("执行完毕,inc的值为:" + inc.get()); } }
AtomicInteger
底层是基于 CAS 的乐观锁实现的,CAS是一种无锁技术,相对于前面的方案,它的效率更高一些,在下面会详细介绍。
执行结果:
执行完毕,inc的值为:1000000
4. 使用 LongAdder 原子类
public class Test { private static LongAdder inc = new LongAdder(); public static void main(String[] args) { // 设置栅栏,保证主线程能获取到程序各个线程全部执行完之后的值 CountDownLatch countDownLatch = new CountDownLatch(1000000); // 设置100个线程同时执行 for (int i = 0; i < 100; i++) { new Thread(() -> { // 循环10000次,对inc实现 +1 操作 for (int j = 0; j < 10000; j++) { inc.increment(); countDownLatch.countDown(); } }).start(); } try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("执行完毕,inc的值为:" + inc.intValue()); } }
LongAdder 原子类在 JDK1.8 中新增的类,其底层也是基于 CAS 机制实现的。适合于高并发场景下,特别是写大于读的场景,相较于 AtomicInteger、AtomicLong 性能更好,代价是消耗更多的空间,以空间换时间。
执行结果:
执行完毕,inc的值为:1000000
CAS理论
讲到现在,终于我们今天的主角要登场了,她就是CAS
。
CAS的意思是比较与交换(Compare And Swap),它是乐观锁的一种实现机制。
什么是乐观锁?通俗的来说就是它比较乐观,每次在修改变量的值之前不认为别的线程会修改变量,每次都会尝试去获得锁,如果获取失败了,它也会一直等待,直到获取锁为止。说白了,它就是打不死的小强。
而悲观锁呢,顾名思义,就比较悲观了,每次在修改变量前都会认为别人会动这个变量,所以它会把变量锁起来,独占,直到自己修改完毕才会释放锁。说白了,就是比较自私,把好东西藏起来自己偷偷享用,完事了再拿出来给别人。像之前的synchronized
关键字就是悲观锁的一种实现。
CAS是一种无锁原子算法,它的操作包括三个操作数:需要读写的内存位置(V)、预期原值(A)、新值(B)。仅当 V值等于A值时,才会将V的值设为B,如果V值和A值不同,则说明已经有其他线程做了更新,则当前线程继续循环等待。最后,CAS 返回当前V的真实值。CAS 操作时抱着乐观的态度进行的,它总是认为自己可以成功完成操作。
CAS的实现
在Java中,JUC的atomic包下提供了大量基于CAS实现的原子类:

我们以AtomicInteger来举例说明。
AtomicInteger类内部通过一个Unsafe类型的静态不可变的变量unsafe来引用Unsafe的实例。
// setup to use Unsafe.compareAndSwapInt for updates private static final Unsafe unsafe = Unsafe.getUnsafe();
然后,AtomicInteger类用value保存自身的数值,并用get()方法对外提供。注意,它的value是使用volatile修饰的,保证了线程的可见性。
private volatile int value; /** * Creates a new AtomicInteger with the given initial value. * * @param initialValue the initial value */ public AtomicInteger(int initialValue) { value = initialValue; } /** * Gets the current value. * * @return the current value */ public final int get() { return value; }
一路跟踪incrementAndGet
方法到的末尾可以看到是一个native的方法:
/** * Atomically increments by one the current value. * * @return the updated value */ public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; } // getAndAddInt 方法 public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; } // compareAndSet方法 public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
可以看到其实incrementAndGet
内部的原理就是通过compareAndSwapInt
调用底层的机器指令不断比较内存旧值和期望的值,如果比较返回false
就继续循环比较,如果返回true
则将当前的新值赋给内存里的值,本次处理完毕。
由此我们知道,原子类实现的自增操作可以保证原子性的根本原因在于硬件(处理器)的相关指令支持。将语义上需要多步操作的行为通过一条指令来完成,CAS指令可以达到这个目的。
Kelemahan CAS
Sebagai pelaksanaan penguncian optimistik, apabila berbilang benang bersaing sengit untuk mendapatkan sumber, berbilang benang akan berputar dan menunggu, yang akan menggunakan sejumlah sumber CPU. -
CAS pasti akan mengalami masalah ABA Untuk penjelasan dan penyelesaian masalah ABA, anda boleh merujuk artikel saya ini: Penemuduga bertanya kepada anda: Adakah anda tahu apa itu masalah ABA?
Baiklah, kali ini perkongsian tentang CAS berakhir di sini. Sebagai asas pengaturcaraan Java, concurrency adalah titik pengetahuan yang sangat penting Jika pelajar mempunyai pemahaman yang lemah tentang topik ini, saya berharap selepas membaca artikel itu, saya boleh menaip kod itu sendiri dan memikirkan apa itu CAS dan apakah itu. kelebihan dan kekurangannya ialah , apakah kaedah pelaksanaannya. Sudah tentu, concurrency adalah konsep yang sangat besar Di sini hanyalah pengenalan kecil, menyebut salah satu mata pengetahuan kecil, dan memberikan beberapa pengalaman pembelajaran saya sendiri. Sekiranya ada apa-apa yang tidak diterangkan dengan betul atau salah, sila hantar mesej peribadi kepada saya untuk membincangkannya bersama, terima kasih! . , di sini kami akan membantu anda mempelajari dan meringkaskan pengetahuan berkaitan Java berdasarkan temu bual sebenar setiap hari, membantu anda mengembangkan timbunan teknologi anda dan meningkatkan kekuatan peribadi anda. Jumpa lagi di lain kali~
Atas ialah kandungan terperinci Soalan temu bual sebenar: Sila bincangkan tentang mekanisme CAS secara serentak. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Alat AI Hot

Undresser.AI Undress
Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover
Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Undress AI Tool
Gambar buka pakaian secara percuma

Clothoff.io
Penyingkiran pakaian AI

AI Hentai Generator
Menjana ai hentai secara percuma.

Artikel Panas

Alat panas

Notepad++7.3.1
Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina
Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1
Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6
Alat pembangunan web visual

SublimeText3 versi Mac
Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

Topik panas



Pengesahan keselamatan PHP melalui CAS (CentralAuthenticationService) Dengan perkembangan pesat Internet, pengurusan hak pengguna dan pengesahan identiti menjadi semakin penting. Apabila membangunkan aplikasi web, adalah penting untuk melindungi data pengguna dan menghalang capaian yang tidak dibenarkan. Untuk mencapai matlamat ini, kami boleh menggunakan CAS (CentralAuthenticationService) untuk pengesahan keselamatan PHP. CAS

1. Jelaskan bahawa apabila berbilang benang melakukan operasi CAS pada sumber pada masa yang sama, hanya satu benang berjaya, tetapi ia tidak akan menyekat benang lain dan benang lain hanya akan menerima isyarat bahawa operasi itu gagal. Ia boleh dilihat bahawa CAS sebenarnya adalah kunci yang optimistik. 2. Mengikuti kod AtomInteger, kita boleh mendapati sum.misc.Unsafe akhirnya dipanggil. Lihat nama Tidak Selamat, ia adalah kelas tidak selamat yang mengeksploitasi hanya lubang yang betul dalam kelas Java dan peraturan keterlihatan. Demi kelajuan, Unsafe membuat beberapa kompromi pada piawaian keselamatan Java. publicfinalnativebooleancompareAndSwapInt(Objec

Penjelasan CAS: CAS (compareandswap), bandingkan dan tukar. Mekanisme yang boleh menyelesaikan kehilangan prestasi yang disebabkan oleh menggunakan kunci dalam situasi selari berbilang benang Operasi CAS mengandungi tiga operan—lokasi memori (V), nilai asal yang dijangkakan (A) dan nilai baharu (B). Jika nilai lokasi memori sepadan dengan nilai asal yang dijangkakan, pemproses secara automatik mengemas kini lokasi kepada nilai baharu. Jika tidak, pemproses tidak melakukan apa-apa. A thread mendapat nilai num dari memori utama dan beroperasi pada num Apabila menulis nilai, thread akan membandingkan nilai num pertama yang diperolehi dengan nilai num dalam ingatan utama, nilai yang berubah akan menjadi num ditulis ke dalam ingatan utama Jika mereka tidak sama, perbandingan akan digelung sehingga berjaya. Dibuat oleh CAS

Selaras terkunci Bagi kebanyakan pengaturcara (sudah tentu saya pada asasnya salah seorang daripada mereka), pengaturcaraan serentak hampir sama dengan menambah kunci (Mutex) pada struktur data yang berkaitan. Sebagai contoh, jika kita memerlukan tindanan yang menyokong konkurensi, cara paling mudah ialah menambah kunci std::sync::Mutex pada tindanan satu benang. (Arc ditambah untuk membenarkan berbilang benang memiliki pemilikan tindanan) usestd::sync::{Mutex,Arc};#[derive(Clone)]structConcurrentStack{inner:Arc,}implConcurrentStack{pubfnnew()-> Self{

Apa itu CASCAS ialah CompareAndSwap, iaitu, bandingkan dan tukar. Mengapa CAS tidak menggunakan kunci tetapi masih memastikan manipulasi data yang selamat di bawah keadaan serentak Nama sebenarnya menunjukkan prinsip CAS dengan sangat intuitif Proses khusus mengubah suai data adalah seperti berikut: Apabila menggunakan CAS untuk mengendalikan data, nilai asal data dan nilai yang akan diubah suai ialah Hantarkannya kepada kaedah untuk membandingkan sama ada nilai pembolehubah sasaran semasa adalah sama dengan nilai asal yang diluluskan. Jika ia adalah sama, ini bermakna pembolehubah sasaran belum diubah suai oleh utas lain. Ubah suai nilai pembolehubah sasaran secara langsung Jika nilai pembolehubah sasaran berbeza daripada nilai asal, maka buktikan pembolehubah sasaran Ia telah diubah suai oleh pengubahsuaian CAS ini, kita dapat melihat bahawa CAS sebenarnya menjamin pengubahsuaian data yang selamat, tetapi terdapat kes di mana pengubahsuaian gagal.

Dalam program ini, saya mencipta 100 utas, dan setiap utas mengumpul 10,000 operasi pada pembolehubah kongsi inc Jika ia dilaksanakan secara serentak, nilai akhir inc hendaklah 1,000,000, tetapi kita tahu bahawa dalam multi-threading, program dilaksanakan serentak. , iaitu, benang yang berbeza mungkin membaca nilai yang sama dari memori utama pada masa yang sama.

1. Buat projek springboot baharu dan perkenalkan dependency org.jasig.cas.clientcas-client-support-springboot3.6.22 Konfigurasikan @EnableCasClient anotasi packagecom.codetiler.demo;importorg.jasig.cas.client.boot.configuration. EnableCasClient;importorg. springframework.boot.SpringApplication;importorg.spring

Isu ini adalah mengenai analisis masalah ABA klasik dalam bidang CAS Saya tidak tahu sama ada anda pernah menemuinya dalam kerja sebenar, tetapi ini adalah fokus ujian pengetahuan serentak dalam temu bual. Jika anda tidak menemui masalah seperti ini, cadangan saya adalah untuk menjalankan sendiri kod di atas.
