Lock接口的主要方法
//嘗試獲取鎖,超時就放棄當前鎖
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
//相當於把超時時間設置爲無限,在等待的過程中,線程可以被打斷
void lockInterruptibly() throws InterruptedException;
//注意不遵守公平原則,會獲取到鎖不管是否會有其它線程等待
boolean tryLock();
//最佳實踐
if (lock.tryLock()) {
try {
// manipulate protected state
} finally {
lock.unlock();
}
}
可見性保證
happens-befor原則:在一個線程解鎖以後,另一個線程加鎖時可以看見之前解鎖線程的操作。
鎖的分類
公平鎖和非公平鎖
定義:公平指的是按照線程的請求順序,來分配鎖。非公平指的是,不完全按照線程的請求順序,在一定情況下可以插隊。
爲什麼有非公平鎖?
避免喚醒的空閒時間。
代碼示例:
package lock.reentrantlock;
import java.util.Random;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 描述: 演示公平和不公平兩種情況
*/
public class FairLock {
public static void main(String[] args) {
PrintQueue printQueue = new PrintQueue();
Thread thread[] = new Thread[10];
for (int i = 0; i < 10; i++) {
thread[i] = new Thread(new Job(printQueue));
}
for (int i = 0; i < 10; i++) {
thread[i].start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Job implements Runnable {
PrintQueue printQueue;
public Job(PrintQueue printQueue) {
this.printQueue = printQueue;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "開始打印");
printQueue.printJob(new Object());
System.out.println(Thread.currentThread().getName() + "打印完畢");
}
}
class PrintQueue {
private Lock queueLock = new ReentrantLock(true);
public void printJob(Object document) {
queueLock.lock();
try {
int duration = new Random().nextInt(10) + 1;
System.out.println(Thread.currentThread().getName() + "正在打印,需要" + duration);
Thread.sleep(duration * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
queueLock.unlock();
}
queueLock.lock();
try {
int duration = new Random().nextInt(10) + 1;
System.out.println(Thread.currentThread().getName() + "正在打印,需要" + duration+"秒");
Thread.sleep(duration * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
queueLock.unlock();
}
}
}
共享鎖(讀鎖)和排它鎖
讀寫鎖的規則:
要麼是多個或者一個線程持有讀鎖,要麼是一個線程有寫鎖,二者不會同時出現。
代碼示例:
package lock.readwrite;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 描述: TODO
*/
public class CinemaReadWrite {
private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
private static void read() {
readLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "得到了讀鎖,正在讀取");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "釋放讀鎖");
readLock.unlock();
}
}
private static void write() {
writeLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "得到了寫鎖,正在寫入");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "釋放寫鎖");
writeLock.unlock();
}
}
public static void main(String[] args) {
new Thread(()->read(),"Thread1").start();
new Thread(()->read(),"Thread2").start();
new Thread(()->write(),"Thread3").start();
new Thread(()->write(),"Thread4").start();
}
}
讀寫鎖插隊策略
- 公平鎖:不允許插隊
- 非公平鎖:寫鎖可以隨時插隊,讀鎖僅在等待隊列的頭節點,不是想獲取寫鎖的線程時可以插隊
鎖的升降級:ReentrantReadWriteLock支持鎖的降級,獲取寫鎖的同時可以獲取讀鎖。
自旋鎖和阻塞鎖
自旋鎖的定義:如果物理機有多個cpu,能夠讓兩個或兩個以上的線程並行執行代碼,我們就可以讓後面那個請求的線程不放棄cpu的執行時間,看看持有鎖的線程是否很快釋放鎖。而爲了讓線程稍等一下,我們需要讓當前線程進行自旋。如果在自旋完成後前面鎖定的同步資源線程已經釋放了鎖,那麼當前線程就可以不必阻塞而是直接獲得同步資源。從而避免切換線程的開銷。
阻塞鎖的定義:如果線程沒有拿到鎖,會直接把線程阻塞,知道被喚醒
自旋鎖的缺點:自旋時間長消耗cpu。
鎖優化
jvm對鎖的優化:自旋鎖和自適應,鎖銷除
代碼層面優化:縮小同步代碼塊,不要鎖住方法,減少鎖的次數