Java線程同步屬于Java多線程與并發程式設計的核心點,需要重點掌握,下面我就來詳解Java線程同步的4種主要的實作方式@mikechen
什麼是線程同步
當使用多個線程來通路同一個資料時,将會導緻資料不準确,互相之間産生沖突,非常容易出現線程安全問題,如下圖所示:
比如多個線程都在操作同一資料,都打算修改商品庫存,這樣就會導緻資料不一緻的問題。
線程同步的真實意思,其實是“排隊”:幾個線程之間要排隊,一個一個對共享資源進行操作,而不是同時進行操作。
是以我們用同步機制來解決這些問題,加入同步鎖以避免在該線程沒有完成操作之前,被其他線程的調用,進而保證了該變量的唯一性和準确性。
線程同步的幾種方式
1、使用synchronized關鍵字
這種方式比較靈活,修飾一個代碼塊,被修飾的代碼塊稱為同步語句塊。
其作用的範圍是大括号{}括起來的代碼,作用的對象是調用這個代碼塊的對象,如下格式:
synchronized(對象) {
//得到對象的鎖,才能操作同步代碼 需要被同步代碼;
}
通常沒有必要同步整個方法,使用synchronized代碼塊同步關鍵代碼即可。
具體的示例如下:
public class SynchronizedThread {
class Bank {
private int account = 200;
public int getAccount() {
return account;
}
/**
* 用同步方法實作
*
* @param money
*/
public synchronized void save(int money) {
account += money;
}
/**
* 用同步代碼塊實作
*
* @param money
*/
public void save1(int money) {
synchronized (this) {
account += money;
}
}
}
class NewThread implements Runnable {
private Bank bank;
public NewThread(Bank bank) {
this.bank = bank;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
// bank.save1(10);
bank.save(10);
System.out.println(i + "賬戶餘額為:" + bank.getAccount());
}
}
}
/**
* 建立線程,調用内部類
*/
public void useThread() {
Bank bank = new Bank();
NewThread new_thread = new NewThread(bank);
System.out.println("線程1");
Thread thread1 = new Thread(new_thread);
thread1.start();
System.out.println("線程2");
Thread thread2 = new Thread(new_thread);
thread2.start();
}
public static void main(String[] args) {
SynchronizedThread st = new SynchronizedThread();
st.useThread();
}
}
如果你還想深入了解Synchronized的底層原理,可以看 Synchronized的實作原理詳解(看這篇就夠了)
2.使用ReentrantLock
ReentrantLock類是可重入、互斥、實作了Lock接口的鎖,它與使用synchronized方法具有相同的基本行為和語義,并且擴充了其能力。
private int account = 100;
//需要聲明這個鎖
private Lock lock = new ReentrantLock();
public int getAccount() {
return account;
}
//這裡不再需要synchronized
public void save(int money) {
lock.lock();
try{
account += money;
}finally{
lock.unlock();
}
}
}
synchronized 與 Lock 的對比
ReentrantLock是顯示鎖,手動開啟和關閉鎖,别忘記關閉鎖;
synchronized 是隐式鎖,出了作用域自動釋放;
ReentrantLock隻有代碼塊鎖,synchronized 有代碼塊鎖和方法鎖;
使用 ReentrantLock鎖,JVM 将花費較少的時間來排程線程,線程更好,并且具有更好的擴充性(提供更多的子類);
優先使用順序:
ReentrantLock> synchronized 同步代碼塊> synchronized 同步方法
3.使用原子變量實作線程同步
為了完成線程同步,我們将使用原子變量(Atomic***開頭的)來實作。
比如典型代表:AtomicInteger類存在于java.util.concurrent.atomic中,該類表示支援原子操作的整數,采用getAndIncrement方法以原子方法将目前的值遞加。
具體示例如下:
private AtomicInteger account = new AtomicInteger(100);
public AtomicInteger getAccount() {
return account;
}
public void save(int money) {
account.addAndGet(money);
}
4.ThreadLocal實作線程同步
如果使用ThreadLocal管理變量,則每一個使用該變量的線程都獲得該變量的副本,副本之間互相獨立,這樣每一個線程都可以随意修改自己的變量副本,而不會對其他線程産生影響,進而實作線程同步。
具體代碼示例如下:
//隻改Bank類,其餘代碼與上同
public class Bank{
// 建立一個線程本地變量 ThreadLocal
private static ThreadLocal<Integer> account = new ThreadLocal<Integer>(){
@Override
//傳回目前線程的"初始值"
protected Integer initialValue(){
return 100;
}
};
public void save(int money){
//設定線程副本中的值
account.set(account.get()+money);
}
public int getAccount(){
//傳回線程副本中的值
return account.get();
}
}
以上
98.5%看過架構技術合集
- 分布式架構設計從0到1全部合集(附:分布式、微服務、高并發等大型網站架構)
- JVM(Java虛拟機)從0到1全部合集(建議收藏)
- Java多線程與并發從0到1全部合集(面試必看)
- Redis分布式緩存從0到1全部合集(進階必看)
- Spring開發架構從0到1全部合集(建議收藏)
- MySQL資料庫從0到1全部合集(建議收藏)