Lock接口
Synchronized關鍵字回顧:
多線程編程步驟(上):
- 創建資源類,在資源類創建屬性和操作方法
- 創建多個線程,調用資源類的操作方法
創建線程的四種方式:
- 繼承Thread
- 實現Runnable接口
- 使用Callable接口
- 使用線程池
使用synchronized同步實現售票問題:
只有當資源是空閑的時候,線程才能訪問。
/**
* 創建資源
*/
class ticket{
private int number = 30;
public synchronized void sell(){
if(number >0){
System.out.println(Thread.currentThread().getName()+":賣出"+number--+"剩余:"+number);
}
}
}
/**
* 創建多個線程,調用資源類的操作方法
*/
public class sellTicket {
public static void main(String[] args) {
/**
* 創建資源
*/
ticket ticket = new ticket();
/**
* 創建三個線程(售票員)
*/
new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 40; i++) {
ticket.sell();
}
}
}, "aaa").start();
new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 40; i++) {
ticket.sell();
}
}
}, "bbb").start();
new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 40; i++) {
ticket.sell();
}
}
}, "ccc").start();
}
}
學習《Java並發編程的藝術》一節是synchronized的實現原理與應用:
Java中的每一個對象都可以作為鎖:
- 對於同步方法,鎖的是當前的對象。
- 對於靜態方法,鎖的是當前類的class對象。
- 對於靜態代碼塊,Synchonized括號里配置的對象。
當一個線程試圖訪問同步代碼塊時,它首先必須得到鎖,退出或拋出異常時必須釋放鎖。
那么鎖到底存在哪里呢?鎖里面會存儲什么信息呢?
這個問題是該書中的一個問題,對個人來說有點抽象,主要點是:JVM基於進入和退出Monitor(監視器)對象來實現方法同步和代碼塊同步,但兩者的實現細節不一樣。
代碼塊同步是使用monitorenter 和monitorexit指令實現的,而方法同步是使用另外一種方式實現的,細節在JVM規范里並沒有 詳細說明。但是,方法的同步同樣可以使用這兩個指令來實現。
monitorenter指令是在編譯后插入到同步代碼塊的開始位置,而monitorexit是插入到方法結 束處和異常處,JVM要保證每個monitorenter必須有對應的monitorexit與之配對。任何對象都有 一個monitor與之關聯,當且一個monitor被持有后,它將處於鎖定狀態。線程執行到monitorenter 指令時,將會嘗試獲取對象所對應的monitor的所有權,即嘗試獲得對象的鎖。
其中monitorenter主要是獲取監視器鎖,monitorexit主要是釋放監視器鎖。
synchonized中的一個概念是Java對象頭:
synchronized用的鎖是存放在Java對象頭里面的。
非數組類型 | 數組類型 | |
虛擬機 | 3個字寬 | 2個字寬 |
需要注意的是:在32位虛擬機中:1字寬=4字節,即:32bit
Java對象頭里的Mark Word里面默認存儲對象HashCode、分代年齡和鎖標記位。
在運行期間,Mark Word里面存儲的數據會隨着鎖標志位的變化而變化。
32位JVM的Mark Word的默認存儲結構與在64位JVM不相同
Lock接口並創建實例:
Java並發編程的藝術
在JavaSE5之前使用的是synchronized關鍵字實現鎖功能,5v之后並發包中新增Lock接口以及相關實現類用來實現鎖功能,提供與synchronized類似的同步關系,但是比其更具有靈活性和擴展性(擁有了鎖獲取與釋放的可操作性、可中斷的獲取鎖以及超時獲取鎖)。
查看Lock的文檔得知:
使用 lock
塊來調用 try
,在之前/之后的構造中,最典型的代碼如下:
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}
根據上述的代碼,實現買票代碼:
import java.util.concurrent.locks.ReentrantLock;
class ticket2{
private int number = 30;
//創建一個重入鎖
private ReentrantLock lock = new ReentrantLock();
public void sale(){
//在內容之前加鎖
lock.lock();
if(number >0){
System.out.println(Thread.currentThread().getName()+":賣出"+number--+"剩余:"+number);
}
//在內容之后解鎖
lock.unlock();
}
}
public class lockCase {
public static void main(String[] args) {
ticket2 tk = new ticket2();
//創建線程
new Thread(()->{
for (int i = 0; i < 40; i++) {
tk.sale();
}
},"aaa").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
tk.sale();
}
},"bbb").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
tk.sale();
}
},"bbb").start();
}
}
上述代碼中,在ticket2類中存在一個問題就是,當lock中間內容如果出現報錯,那么后面的代碼無法執行,也就是鎖無法釋放。所以我們需要將unlock()放到finally中。
目的是保證在獲取到鎖之后,最終能夠被釋放。
public void sale(){
//在內容之前加鎖
lock.lock();
try{
if(number >0){
System.out.println(Thread.currentThread().getName()+":賣出"+number--+"剩余:"+number);
}
//在內容之后解鎖
}finally{
lock.unlock();
}
}