出現:JDK1.5 實作類: ReentrantLock(重入鎖、手工鎖)
ReentrantReadWriteLock.ReadLock ReentrantReadWriteLock.WriteLock 主要方法: lock() unlock() 使用: TestLock.java
1
public class TestLock implement Runnable{
2
3
ReentrantLock lock = new ReentrantLock();
4
5
@Override
6
public void run(){
7
8
try{
9
//加鎖
10
lock.lock();
11
//執行同步代碼
12
}
13
finally{
14
//釋放鎖
15
lock.unlock();
16
}
17
18
}
和synchronized互斥鎖的比較: (1)synchronized關鍵字修飾的代碼是完全互斥的,而Lock更加 靈活,可以實作多線程同時讀共享資料(讀鎖和讀鎖不互斥),但讀鎖和寫鎖互斥。寫鎖和寫鎖互斥( 讀寫鎖)。 (2)ReentrainLock有 tryLock方法:
1
public class ReentrainLockTest{
2
//擷取鎖
3
Lock lock = new ReentrainLock();
4
//線程1的方法:擷取鎖
5
void m1(){
6
lock.lock();//鎖定
7
try{
8
for(int i=0;i<10;i++){//鎖定10秒
9
TimeUtil.SECONDS.sleep(1);//sleep 1秒
10
}
11
}catch(InterruptException e){
12
e.printStackTrace();
13
}finally{
14
lock.unlock();//10秒後釋放鎖
15
}
16
}
17
//線程2的方法
18
void m2(){
19
boolean locked = lock.tryLcok();//嘗試鎖定,如果傳回為false,則擷取鎖失敗,但程式繼續執行。
20
Systerm.out.println("是否獲得鎖:" + locked);
21
if(locked) lock.unlock();//如果獲得鎖,則解鎖
22
}
23
24
public static void main(String[] args){
25
//開啟兩個線程,線程1調用m1方法,線程2調用m2方法
26
}
27
}
(3)lockInterruptibly方法持有的鎖,如果沒擷取到鎖可被其它線程打斷
1
public class{
2
public static void main(Stirng[] args){
3
Lock lock = new ReentrainLock();
4
//1.建立t1線程,一直持有鎖
5
Thread t1 = new Thread(()-> {
6
lock.lock();
7
try{
8
while(true){//一直持有鎖
9
TimeUtils.SECOND.sleep(1);
10
}
11
}catch(InterruptException e){
12
e.printStackTrace();
13
}fainally{
14
lock.unlock();//釋放鎖
15
}
16
});
17
t1.start();
18
19
//2.啟動t2線程,嘗試擷取鎖,但是一直擷取失敗(t1線程一直持有鎖)
20
Tread t2 = new Thread(()->{
21
try{
22
boolean isLocked = lock.tryLock();
23
lock.lockInterruptibly();//表示可以對其它線程的interrupt方法做出響應
24
System.out.println("t2線程啟動!");
25
}catch(InterruptException e){
26
e.printStackTrace();
27
}finally{
28
if(isLocked) lock.unlock();
29
}
30
31
});
32
t2.start();
33
34
//3.主線程1秒後打斷t2線程的等待鎖狀态,t2線程結束,抛出異常
35
try{
36
timeUtils.SECONDS.sleep(1)
37
}catch(InterruptException e){
38
e.printStackTrace();
39
}
40
t2.interrupt();
41
}
42
}
(4)reentrianLock可以指定公平鎖(synchronized為不公平鎖) synchronized在鎖釋放時,所有線程都在競争鎖(不确定誰能拿到鎖),很可能有的線程一次都拿不到,不公平
reentrianLock可以在鎖競争時,判斷哪個線程等待的時間長,就先讓哪個線程擷取鎖
1
Lock lock = new ReentrainLock(true);//傳入true為公平鎖,預設為非公平鎖
讀寫鎖接口: ReadWriteLock 實作類: ReentranReadWriteLock 方法: readLock、writeLock(傳回Lock對象) 使用:
1
ReadWriteLock rwl = new ReenTranReadWriteLock();
2
rwl.readLock().lock();//擷取讀鎖
3
rwl.readLock().unLock();//釋放讀鎖
4
5
rwl.writeLock().lock();//擷取寫鎖
6
rwl.writeLock().unLock();//釋放寫鎖
例子:使用者從銀行讀取餘額(讀鎖,多使用者同時讀一個賬号)。使用者從銀行存取錢(寫鎖,每次隻能一個使用者從同一個賬号存取錢)
使用者賬戶類:MyAccount.java
1
public class MyCount {
2
private String oid; //賬号
3
private int cash; //賬戶餘額
4
5
MyCount(String oid, int cash) {
6
this.oid = oid;
7
this.cash = cash;
8
}
9
10
public String getOid() {
11
return oid;
12
}
13
14
public void setOid(String oid) {
15
this.oid = oid;
16
}
17
18
public int getCash() {
19
return cash;
20
}
21
22
public void setCash(int cash) {
23
this.cash = cash;
24
}
25
26
@Override
27
public String toString() {
28
return "MyCount{" +
29
"oid='" + oid + '\'' +
30
", cash=" + cash +
31
'}';
32
}
33
}
線程類(多使用者同時通路一個賬号):UserThread.java
1
public class UserThread implements Runnable {
2
private String name; //使用者名
3
private MyCount myCount; //所要操作的賬戶
4
private int iocash; //操作的金額,當然有正負之分了
5
private ReadWriteLock myLock; //執行操作所需的鎖對象
6
private boolean ischeck; //是否查詢
7
8
public UserThread(String name, MyCount myCount, int iocash, ReadWriteLock myLock, boolean ischeck) {
9
this.name = name;
10
this.myCount = myCount;
11
this.iocash = iocash;
12
this.myLock = myLock;
13
this.ischeck = ischeck;//是否檢視餘額
14
}
15
16
public void run() {
17
if (ischeck) { //檢視餘額
18
//擷取讀鎖
19
myLock.readLock().lock();
20
System.out.println("讀:" + name + "正在查詢" + myCount + "賬戶,目前金額為" + myCount.getCash());
21
//釋放讀鎖
22
myLock.readLock().unlock();
23
} else { //存取錢
24
//擷取寫鎖
25
myLock.writeLock().lock();
26
//執行現金業務
27
System.out.println("寫:" + name + "正在操作" + myCount + "賬戶,金額為" + iocash +",目前金額為" + myCount.getCash());
28
myCount.setCash(myCount.getCash() + iocash);
29
System.out.println("寫:" + name + "操作" + myCount + "賬戶成功,金額為" + iocash +",目前金額為" + myCount.getCash());
30
//釋放寫鎖
31
myLock.writeLock().unlock();
32
}
33
}
34
}
測試類:Test.java
1
public class Test {
2
public static void main(String[] args) {
3
//建立并發通路的賬戶
4
MyCount myCount = new MyCount("95599200901215522", 10000);
5
//建立一個鎖對象
6
ReadWriteLock lock = new ReentrantReadWriteLock(false);
7
//建立一個線程池
8
ExecutorService pool = Executors.newFixedThreadPool(2);
9
//建立一些并發通路使用者,一個信用卡,存的存,取的取,好熱鬧啊
10
User u1 = new User("Mike", myCount, -4000, lock, false);
11
User u2 = new User("Mike's Dad", myCount, 6000, lock, false);
12
User u3 = new User("Mike's Brother", myCount, -8000, lock, false);
13
User u4 = new User("Mike's Mom", myCount, 800, lock, false);
14
User u5 = new User("Mike's son", myCount, 0, lock, true);
15
//線上程池中執行各個使用者的操作
16
pool.execute(u1);
17
pool.execute(u2);
18
pool.execute(u3);
19
pool.execute(u4);
20
pool.execute(u5);
21
//關閉線程池
22
pool.shutdown();
23
}
24
}
鎖更新和降級原則: 讀鎖不可更新成寫鎖(讀鎖釋放前不可以加寫鎖)
寫鎖可以降級成讀鎖(寫鎖釋放前可以加讀鎖)
官方文檔讀寫鎖例子:
1
class CachedData {
2
Object data;
3
volatile boolean cacheValid;
4
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
5
6
void processCachedData() {
7
rwl.readLock().lock();
8
if (!cacheValid) {//第一重檢測,沒有緩存資料,釋放讀鎖,加寫鎖寫資料
9
// 在寫鎖前必須釋放讀鎖
10
rwl.readLock().unlock();
11
rwl.writeLock().lock();
12
try {
13
//第二重檢測,寫資料前再次確定緩存沒有資料,防止在第10行釋放了讀鎖後,被其它線程的寫鎖拿到,往緩存裡寫了資料
14
if (!cacheValid) {
15
data = ...//資料庫寫入修改操作
16
cacheValid = true;
17
}
18
// 鎖降級,在寫鎖裡面先上讀鎖,把寫鎖降級成讀鎖
19
rwl.readLock().lock();
20
} finally {
21
rwl.writeLock().unlock(); // 在讀鎖狀态下釋放寫鎖
22
}
23
}
24
25
try {
26
use(data);
27
} finally {
28
rwl.readLock().unlock();
29
}
30
}
31
}