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 意思為可重入鎖,指的是一個線程能夠對一個臨界資源重復加鎖。
其原理是使用AQS(AbstractQueuedSynchornized)機制,是采用雙向隊列來進行加鎖。
AQS機制
AQS 是一個設計模板,具體的實現由子類完成,AQS中維護了一個state(共享資源,代表是否可以被加鎖)和一個CLH(先入先出)雙向隊列。把需要等待的線程放進CLH隊列。
ReentrantLock 實現原理
ReentrantLock支持公平鎖和非公平鎖,其中的一些原理是稍微由不同的。
公平鎖
ReentrantLock(true)
reentrantLock.lock(); reentrantLock.unlock();
線程加鎖,先把線程加入隊列中,如果該線程是頭節點,就加鎖執行,如果不是調用本地方法park等待。線程解鎖,更新狀態,並且從尾節點向上追溯到head的下一個節點,並且更新head節點。
非公平鎖
ReentrantLock() ReentrantLock(false)
reentrantLock.lock(); reentrantLock.unlock();
AQS中維護了一個volatile int state(共享資源)和一個CLH隊列。當state=1時代表當前對象鎖已經被占用,其他線程來加鎖時則會失敗,失敗的線程被放入一個FIFO的等待隊列中,然后會被UNSAFE.park()操作掛起,等待已經獲得鎖的線程釋放鎖才能被喚醒。
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();
}
}