Lock鎖介紹:
在java中可以使用 synchronized 來實現多線程下對象的同步訪問,為了獲得更加靈活使用場景、高效的性能,java還提供了Lock接口及其實現類ReentrantLock和讀寫鎖 ReentrantReadWriteLock。
相比synchronized來實現同步,使用Lock實現同步主要有以下差異性:
1、使用synchronized關鍵字時,鎖的控制和釋放是在synchronized同步代碼塊的開始和結束位置。而在使用Lock實現同步時,鎖的獲取和釋放可以在不同的代碼塊、不同的方法中。這一點是基於使用者手動獲取和釋放鎖的特性。
2、Lock接口提供了試圖獲取鎖的tryLock()方法,在調用tryLock()獲取鎖失敗時返回false,這樣線程可以執行其它的操作 而不至於使線程進入休眠。tryLock()方法可傳入一個long型的時間參數,允許在一定的時間內來獲取鎖。
3、Lock接口的實現類ReentrantReadWriteLock提供了讀鎖和寫鎖,允許多個線程獲得讀鎖、而只能有一個線程獲得寫鎖。讀鎖和寫鎖不能同時獲得。實現了讀和寫的分離,這一點在需要並發讀的應用中非常重要,如lucene允許多個線程讀取索引數據進行查詢但只能有一個線程負責索引數據的構建。
4、基於以上3點,lock來實現同步具備更好的性能。
Lock鎖與條件同步:
與synchronized類似,Lock鎖也可以實現條件同步。在java的concurrent包中提供了 Condition 接口及其實現類ConditionObject。
當滿足一定條件時,調用Condition的await()方法使當前線程進入休眠狀態進行等待。調用Condition的signalAll()方法喚醒因await()進入休眠的線程。
在synchronized與條件同步博文中,我們使用synchronized實現了一個生產者-消費者模型,在這里,來試試使用Lock鎖及其同步條件來實現同樣的一個生產者-消費者模型:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Goods{
private String goodsName;
private Integer count=0;
private Integer full;
//獲得lock對象
private Lock lock=new ReentrantLock();
//獲得生產者等待隊列對象
private Condition productorCondition=lock.newCondition();
//獲得消費者等待隊列對象
private Condition customerCondition=lock.newCondition();
public Goods(Integer full) {
this.full = full;
}
public void setGoods(String goodsName){
try{
//使用鎖實現同步,獲取所得操作,當鎖被其他線程占用時,當前線程將進入休眠
lock.lock();
while (this.count==this.full){
System.out.println("商品庫存很多,快來購買哦");
try {
//滿足條件時,線程休眠並釋放鎖。當調用 signalAll()時。線程喚醒並重新獲得鎖
productorCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.goodsName=goodsName;
this.count++;
System.out.println(Thread.currentThread().getName()+"生產"+this.toString());
//喚醒因productorCondition.await()休眠的線程
customerCondition.signalAll();
}finally {
lock.unlock();//解鎖
}
}
public void getGoods(){
try {
lock.lock();
while (this.count==0){
System.out.println("商品正在生產中,親稍等哦");
try {
customerCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.count--;
System.out.println(Thread.currentThread().getName()+"消費"+this.toString());
//這個時候已經生產完了,生產者喚醒消費者進行消費
productorCondition.signalAll();
}finally {
lock.unlock();
}
}
@Override
public String toString() {
return "Goods{" +
"goodsName='" + goodsName + '\'' +
", count=" + count +
'}';
}
}
class Productor implements Runnable{
private Goods goods;
public Productor(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
while (true){
this.goods.setGoods("mac");
}
}
}
class Customer implements Runnable{
private Goods goods;
public Customer(Goods goods) {
this.goods = goods;
}
@Override
public void run() {
while (true){
this.goods.getGoods();
}
}
}
public class Test{
public static void main(String[] args) {
Goods goods=new Goods(20);
Productor productor=new Productor(goods);
Customer customer=new Customer(goods);
List<Thread> list=new ArrayList<>();
for(int i=0;i<5;i++){
Thread thread=new Thread(productor,"生產者"+i);
list.add(thread);
}
for(int i=0;i<10;i++){
Thread thread=new Thread(customer,"消費者"+i);
list.add(thread);
}
for(Thread thread:list){
thread.start();
}
}
}
//控制台打印結果部分截圖
總結:
不管是synchronized關鍵字還是Lock鎖,都是用來在多線程的環境下對資源的同步訪問進行控制,用以避免因多個線程對數據的並發讀寫造成的數據混亂問題。與synchronized不同的是,Lock鎖實現同步時需要使用者手動控制鎖的獲取和釋放,其靈活性使得可以實現更復雜的多線程同步和更高的性能,但同時,使用者一定要在獲取鎖后及時捕獲代碼運行過程中的異常並在finally代碼塊中釋放鎖。
