一、介紹
當我們寫併發編程時,多個線程可同時訪問一個共享資源,比如變量或對象,如果多個線程同時讀寫該資源,會導致該資源狀態混亂,數據不準確,相互之間產生衝突。
因此加入同步鎖,使資源同一時間只能有一個線程訪問,從而保證資源不發生衝突。
二、線程同步具體實現
1、Synchronized
使用Synchronized關鍵字,分爲同步方法和同步代碼塊,具體關於Synchronized的介紹和使用請移步《Synchronized簡單介紹(原理、使用、對比分析)》
2、volatile
volatile的中文意思是不穩定的、反覆無常的、異變的。
a、volatile是輕量級同步機制,在訪問volatile修飾的變量時,不會執行加鎖操作,所以也就不能使線程阻塞,是一種比synchronized關鍵字更輕量級的同步機制。
b、volatile只能保證可見性,不能保證原子性;加鎖機制即可以保證可見性又可以保證原子性,所以votatile並不能替代synchronized。
volatile的中文意思是不穩定的,反覆無常的,異變的。當使用volatile聲明變量後,系統總是重新從它所在的內存中讀取數據,而非從緩存中讀取,從而保證了數據在內存中的可見性,這就保證了同步。(通過這裏也可以看出,volatile修飾的變量都是可變的,不能修飾final類型的變量)
c、volatile會禁止指令重排,屏蔽代碼優化,進而造成效率降低,所以只有在必要時纔可用該關鍵字。
3、使用原子變量
原子變量保證了原子操作(所謂原子操作,就是數據的讀取、修改、保存作爲一個整體行爲,這個整體行爲要麼都執行,要麼都不執行,是不可被分割、不可中斷的,不能僅僅執行其中的一部分動作)
需要線程同步的根本原因就是因爲普通變量的操作不是原子的,而原子變量保證了原子性,所以能保證同步。
在Java的util.concurrent.atomic包中提供了創建原子類型變量的工具類,使用這些原子類可以簡化線程同步。
常用的原子變量類型有:
基本類型對應:AtomicBoolean、AtomicInteger、AtomicLong
數組類型對應:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
引用類型對應:AtomicReference
4、使用ThreadLocal類
ThreadLocal通過字面直接翻譯,就是線程本地 ,也就是線程本地變量、線程局部變量。
ThreadLocal的作用域僅僅是本線程內,即ThreadLocal爲每個線程提供了一個特定的變量,以保存該線程所獨享的數據,所以其提供了一種隔離線程,防止線程間數據共享的方法。
詳細介紹請移步《ThreadLocal解析》。
5、使用阻塞隊列
阻塞隊列(BlockingQueue)和普通隊列的區別就是阻塞,所謂阻塞,即當隊列是空時,從隊列中取數據(take())的操作會被阻塞,或當隊列已經滿時,向隊列存數據(put(E, e))的操作將會被阻塞。當存取數據隊列被阻塞時,即表示當前線程阻塞。
阻塞隊列的常見實現類如ArrayBlockingQueue,LinkedBlockingQueeu,PriorityBlockingQueue等。
阻塞隊列的使用場景典型的是生產者消費者模式,生產者線程向阻塞隊列中存放數據,消費者線程從該阻塞隊列中取出數據。
public class MainActivity extends AppCompatActivity {
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<String>(10);
Button mBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBtn = (Button) findViewById(R.id.mBtn);
mBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Producer(queue).start();
new Consumer(queue).start();
}
});
}
//生產者
class Producer extends Thread {
ArrayBlockingQueue<String> mQueue;
public Producer(ArrayBlockingQueue<String> queue) {
mQueue = queue;
}
@Override
public void run() {
super.run();
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(50);
mQueue.put("Production" + i);
System.out.println("Producer:Production" + i );
} catch (Exception e) {
e.getMessage();
}
}
}
}
//消費者
class Consumer extends Thread {
ArrayBlockingQueue<String> mQueue;
public Consumer(ArrayBlockingQueue<String> queue) {
mQueue = queue;
}
@Override
public void run() {
super.run();
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(50);
String str = mQueue.take();
System.out.println("Consumer: " + str );
} catch (Exception e) {
e.getMessage();
}
}
}
}
}
執行結果如下:
6、使用Lock
主要就是使用重入鎖ReentrantLock類 ,其實現了Lock接口。
常用的方法:
創建ReentrantLock()實例:ReentrantLock lock = new ReentrantLock();
獲得鎖:lock.lock();
釋放鎖:lock.unlock();
代碼 :
//創建實例
ReentrantLock lock = new ReentrantLock();
lock.lock();
try{
//doSomething
}finally{
lock.unlock();
}
通過代碼可以看到,使用ReentrantLock,必須自己實現獲得/釋放鎖,從而帶來了風險,顯然不如直接使用synchronized.