在Java多線程編程當(dāng)中,提供了以下幾種方式來實(shí)現(xiàn)線程安全:
Synchronized--阻塞
內(nèi)部鎖(Synchronized)和顯式鎖(Lock):屬于互斥同步方法,是重量級(jí)的多線程同步機(jī)制,可能會(huì)引起上下文切換和線程調(diào)度,它同時(shí)提供內(nèi)存可見性、有序性和原子性。
Volatile--非阻塞
volatile:輕量級(jí)多線程同步機(jī)制,不會(huì)引起上下文切換和線程調(diào)度。僅提供內(nèi)存可見性、有序性保證,不提供原子性。
CAS--非阻塞
CAS原子指令:屬于非阻塞同步方法,輕量級(jí)多線程同步機(jī)制,不會(huì)引起上下文切換和線程調(diào)度。它同時(shí)提供內(nèi)存可見性、有序性和原子化更新保證。單個(gè)變量的更新就可以用原子操作
1.Synchronized, 在方法上添加 Synchronized 修飾
還是小華,小明,小航,要到淘寶最全世界最酷炫的LV 小褲褲,但是現(xiàn)在存貨只有兩條了,自然就是手快有,手慢無啦
public static void main(String args[]){
Order order=new Order();
//啟動(dòng)3個(gè)線程,代表他們進(jìn)行下單
Thread thread1=new Thread(order,"小華");
Thread thread2=new Thread(order,"小航");
Thread thread3=new Thread(order,"小明");
thread1.start();
thread2.start();
thread3.start();
}
public static class Order implements Runnable{
private int lvUnderWare=2; //褲子的庫存只有兩條了
@Override
public void run() {
if (lvUnderWare > 0) {
System.out.println(Thread.currentThread().getName() + "開始下單買LV的小褲褲," + "訂單顯示還剩下:" + lvUnderWare + "條");
lvUnderWare--;
System.out.println(Thread.currentThread().getName() + "下單成功了,買到lv的小褲褲了");
} else {
System.out.println(Thread.currentThread().getName()+"也要購買,但是訂單不足了");
try {
Thread.sleep(1000 * 60 );
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

可以看到訂單顯示0條的時(shí)候,還能下單成功?出問題了呀,因?yàn)樾『劫I的時(shí)候,小明也開始買了呀,但是小明下手慢呀,就木得了,所以這個(gè)訂單出現(xiàn)線程安全得問題了,我們加個(gè)同步方法吧
@Override
public synchronized void run() {
if (lvUnderWare > 0) {
System.out.println(Thread.currentThread().getName() + "開始下單買LV的小褲褲," + "訂單顯示還剩下:" + lvUnderWare + "條");
lvUnderWare--;
System.out.println(Thread.currentThread().getName() + "下單成功了,買到lv的小褲褲了");
} else {
System.out.println(Thread.currentThread().getName()+"也要購買,但是訂單不足了");
try {
Thread.sleep(1000 * 60 );
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

可以看到這回小明,買不到 了,因?yàn)橐呀?jīng)賣完了呀
2.Synchronized 修飾代碼塊
@Override
public void run() {
if (lvUnderWare > 0) {
synchronized (this){
System.out.println(Thread.currentThread().getName() + "開始下單買LV的小褲褲," + "訂單顯示還剩下:" + lvUnderWare + "條");
lvUnderWare--;
System.out.println(Thread.currentThread().getName() + "下單成功了,買到lv的小褲褲了");
}
}
else {
System.out.println(Thread.currentThread().getName()+"也要購買,但是訂單不足了");
try {
Thread.sleep(1000 * 60 );
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
代表獲取當(dāng)前對(duì)象的鎖;也可以是類中的一個(gè)屬性對(duì)象,代表獲取該屬性對(duì)象的鎖。我得理解就是order 類這個(gè)對(duì)象得鎖,得到得結(jié)果也是小明買不到了,也是線程安全得

3.修飾類
synchronized (Order.class)
@Override
public void run() {
synchronized (Order.class) {
if (lvUnderWare > 0) {
System.out.println(Thread.currentThread().getName() + "開始下單買LV的小褲褲," + "訂單顯示還剩下:" + lvUnderWare + "條");
lvUnderWare--;
System.out.println(Thread.currentThread().getName() + "下單成功了,買到lv的小褲褲了");
} else {
System.out.println(Thread.currentThread().getName() + "也要購買,但是訂單不足了");
try {
Thread.sleep(1000 * 60);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
一樣得到得是線程安全的
Volicate
線程在寫入volatile變量得時(shí)候不會(huì)把值緩存在寄存器或者其他地方,而是會(huì)把值刷新回主內(nèi)存中,當(dāng)其他內(nèi)存讀取該變量得時(shí)候,會(huì)從主內(nèi)存中重新獲取該值,而不是從當(dāng)前線程的工作內(nèi)存中獲取值,實(shí)際上就是,線程寫入了volicate 變量的時(shí)候就相當(dāng)于線程退出同步的代碼塊,將變量的值寫入到主內(nèi)存中,讀取volicate變量的時(shí)候就詳單與進(jìn)入同步塊,會(huì)清空本地內(nèi)存的變量的值,然后從主內(nèi)存中獲取最新的值
private volatile static int count=1;
public static void main (String [] args) throws InterruptedException {
int finalI=1;
for(int i=1;i<=10;i++){
new Thread(()->{
count++;
System.out.println(count);
}).start();
}
Thread.sleep(1000);
System.out.println("final:"+count);
}

可以看到中間的count 的值是線程不安全的,但是最終得到的值永遠(yuǎn)都是 11
所以使用volicate 我覺得如果是要關(guān)注每個(gè)線程中間的變量值是不適合的,所以說當(dāng)寫入變量值要依賴當(dāng)前變量值,這種操作不是原子性的,而volicate不保證原子性的
鎖釋放
異常自動(dòng)釋放鎖
當(dāng)一個(gè)線程執(zhí)行的代碼出現(xiàn)異常時(shí),其所持有的鎖會(huì)自動(dòng)釋放。