java 幾種同步方式
java的同步方式
在java主要有以下幾種方式
1.給變數/程式碼塊/方法/類新增悲觀鎖避免一個變數更改值的時候對這個變數進行更改。
用 sychronized 來進行同步
由於java的每個物件都有一個內建鎖,當用此關鍵字修飾方法時, 內建鎖會保護整個方法。在呼叫該方法前,需要獲得內建鎖,否則就處於阻塞狀態。
程式碼如:
public synchronized void save(){}
注: synchronized關鍵字也可以修飾靜態方法,此時如果呼叫該靜態方法,將會鎖住整個類
優缺點:
優點:加上synchronized鎖是為了保證伺服器選擇的順序性,使得併發操作的同一時刻有且僅有一個執行緒能夠讀取/寫入資料表記錄,防止多執行緒併發造成的髒資料。
缺點:悲觀鎖的引入,將會導致併發吞吐量發生明顯下降,付出相當大的效能代價。且不能獲得鎖是否是已經加上去了。
2.使用特殊域變數(volatile)實現執行緒同步
它的原理是每次要執行緒要訪問volatile修飾的變數時都是從記憶體中讀取,而不是存快取當中讀取,因此每個執行緒訪問到的變數值都是一樣的。這樣就保證了同步
a.volatile關鍵字為域變數的訪問提供了一種免鎖機制,
b.使用volatile修飾域相當於告訴虛擬機器該域可能會被其他執行緒更新,
c.因此每次使用該域就要重新計算,而不是使用暫存器中的值
d.volatile不會提供任何原子操作,它也不能用來修飾final型別的變數
因為volatile使用了複合操作的原子性,所以下面的程式碼結果一定<=正確值
/** * 使用特殊域變數(volatile)實現執行緒同步 * a.volatile關鍵字為域變數的訪問提供了一種免鎖機制, * b.使用volatile修飾域相當於告訴虛擬機器該域可能會被其他執行緒更新, * c.因此每次使用該域就要重新計算,而不是使用暫存器中的值 * d.volatile不會提供任何原子操作,它也不能用來修飾final型別的變數 */ public class VolatileKeywordSynchronization { class Bank { private volatile int accout = 10; public int getAccout() { return accout; } public void saveAccount(int money) { accout += money; System.out.println("account:" + accout); } } class VolatileThread implements Runnable { private Bank bank; public VolatileThread(Bank bank) { this.bank = bank; } @Override public void run() { for (int i = 0; i < 10; i++) { bank.saveAccount(10); System.out.println(Thread.currentThread().getName() + "-->第" + i + "次當前賬戶餘額:" + bank.getAccout() + "元。"); } } } public void userVolatileThread() { Bank bank = new Bank(); VolatileThread volatileThread = new VolatileThread(bank); Thread thread1 = new Thread(volatileThread); Thread thread2 = new Thread(volatileThread); System.out.println("執行緒1:"); thread1.start(); System.out.println("執行緒2:"); thread2.start(); } public static void main(String[] args) { VolatileKeywordSynchronization volatileKeywordSynchronization = new VolatileKeywordSynchronization(); volatileKeywordSynchronization.userVolatileThread(); } }
volatile的優缺點
優點:Volatile修飾的成員變數在每次被執行緒訪問是,都強迫從共享記憶體中重讀成員變數的值;當成員變數發生變化是,強迫執行緒將變化值回寫到共享記憶體;這樣可以讓多個執行緒總是看到某個成員變數的同一個值,Volatile關鍵字就是提示VM:對於這個成員變數不能儲存它的私有拷貝,而應該直接語共享成員變數互動;
缺點: 關鍵字能保證資料的可見性,但不能保證資料的原子性,且volatile只適用變數。
3.AtomicInteger(類)
public class VolatileKeywordSynchronization { class Bank { private AtomicInteger accout = new AtomicInteger(10); public int getAccout() { return accout.get(); } public void saveAccount(int money) { accout.getAndAdd(money); System.out.println("account:" + accout); } } class VolatileThread implements Runnable { private Bank bank; public VolatileThread(Bank bank) { this.bank = bank; } @Override public void run() { for (int i = 0; i < 10; i++) { bank.saveAccount(10); System.out.println(Thread.currentThread().getName() + "-->第" + i + "次當前賬戶餘額:" + bank.getAccout() + "元。"); } } } public void userVolatileThread() { Bank bank = new Bank(); VolatileThread volatileThread = new VolatileThread(bank); Thread thread1 = new Thread(volatileThread); Thread thread2 = new Thread(volatileThread); System.out.println("執行緒1:"); thread1.start(); System.out.println("執行緒2:"); thread2.start(); } public static void main(String[] args) { VolatileKeywordSynchronization volatileKeywordSynchronization = new VolatileKeywordSynchronization(); volatileKeywordSynchronization.userVolatileThread(); } }
AtomicInteger 類主要利用 CAS (compare and swap) + volatile 和 native 方法來保證原子操作,從而避免 synchronized 的高開銷,執行效率大為提升。 CAS 的原理是拿期望的值和原本的一個值作比較,如果相同則更新成新的值。UnSafe 類的 objectFieldOffset() 方法是一個本地方法,這個方法是用來拿到“原來的值”的記憶體地址,返回值是 valueOffset。另外 value 是一個 volatile 變數,在記憶體中可見,因此 JVM 可以保證任何時刻任何執行緒總能拿到該變數的最新值。
優缺點:
優點:不需要加鎖,就可以保證原子性,減少大量的開銷。
缺點:不能保證程式碼塊的原子性
ReentrantLock
ReentrantLock 意思為可重入鎖,指的是一個執行緒能夠對一個臨界資源重複加鎖。
public class VolatileKeywordSynchronization {
ReentrantLock reentrantLock = new ReentrantLock();
class Bank {
private int accout = 10;
public int getAccout() {
return accout;
}
public void saveAccount(int money) {
reentrantLock.lock();
try {
accout+=money;
}finally {
reentrantLock.unlock();
}
System.out.println("account:" + accout);
}
}
class VolatileThread implements Runnable {
private Bank bank;
public VolatileThread(Bank bank) {
this.bank = bank;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
bank.saveAccount(10);
System.out.println(Thread.currentThread().getName() + "-->第" + i + "次當前賬戶餘額:" + bank.getAccout() + "元。");
}
}
}
public void userVolatileThread() {
Bank bank = new Bank();
VolatileThread volatileThread = new VolatileThread(bank);
Thread thread1 = new Thread(volatileThread);
Thread thread2 = new Thread(volatileThread);
System.out.println("執行緒1:");
thread1.start();
System.out.println("執行緒2:");
thread2.start();
}
public static void main(String[] args) {
VolatileKeywordSynchronization volatileKeywordSynchronization = new VolatileKeywordSynchronization();
volatileKeywordSynchronization.userVolatileThread();
}
}