鎖是在多線程中對公共資源的控制。(java.util.concurrent.locks
)
locks包下的接口:
|---AbstractOwnableSynchronizer
|---AbstractQueuedLongSynchronizer
|---AbstractQueuedSynchronizer
|---Condition
|---Lock
|---LockSupport
|---ReadWriteLock
|---ReentrantLock
|---ReentrantReadWriteLock
樂觀鎖和悲觀鎖
樂觀鎖: 樂觀鎖會認爲每次查詢都不會造成更新丟失,所以每次都不會加鎖,利用版本控制實現。
悲觀鎖: 該鎖認爲每次操作都會對數據造成更新丟失,每次查詢時加上排它鎖。
鎖的類型
- 可重入鎖
同一個線程中,外層函數獲得鎖,內層依然有該鎖的代碼,不受影響。
public class Hello implements Runnable{
ReentrantLock lock = new ReentrantLock();
private void worker(){
lock.lock();
System.out.println("I'm worker: " + Thread.currentThread().getId());
sayHello();
lock.unlock();
}
private void sayHello(){
lock.lock();
System.out.println("Hello Word");
lock.unlock();
}
@Override
public void run() {
worker();
}
public static void main(String[] args) {
Hello hello = new Hello();
new Thread(hello).start();
new Thread(hello).start();
new Thread(hello).start();
}
}
- 自旋鎖
同一個線程兩次調用lock(),會導致第二次調用lock()位置進行自旋,產生死鎖。(不可重入鎖)
public class SpinLock {
private AtomicReference cas = new AtomicReference();
public void lock() {
Thread current = Thread.currentThread();
// 利用CAS
while (!cas.compareAndSet(null, current)) {
// DO nothing
}
}
public void unlock() {
Thread current = Thread.currentThread();
cas.compareAndSet(current, null);
}
}
- 可中斷鎖
在等待獲取鎖的過程中可中斷公平鎖
public class Hello implements Runnable{
ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
try {
lock.lockInterruptibly();
System.out.println("Hello Word");
Thread.sleep(2000);
lock.unlock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
//正常
Hello hello = new Hello();
Thread t1 = new Thread(hello);
t1.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.interrupt();
// InterruptedException異常
// Hello hello = new Hello();
// Thread t1 = new Thread(hello);
// t1.start();
// t1.interrupt();
}
}
- 公平鎖
按等待獲取鎖的線程的等待時間進行獲取,等待時間長的優先獲取。(ReentrantLock默認是公平鎖
)
非公平鎖
public class Hello implements Runnable{
//非公平鎖
ReentrantLock lock = new ReentrantLock(false);
@Override
public void run() {
try {
lock.lock();
System.out.println("Hello Word" + Thread.currentThread().getName());
Thread.sleep(2000);
lock.unlock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Hello hello = new Hello();
Thread t1 = new Thread(hello);
t1.setName("t1");
t1.start();
Thread t2 = new Thread(hello);
t2.setName("t2");
t2.start();
Thread t3 = new Thread(hello);
t3.setName("t3");
t3.start();
}
}
- 讀寫鎖
對資源的讀寫的時候拆成兩個部分處理。讀的時候可以多線程一起讀,寫的時候必須同步寫。
public class Hello {
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public static void main(String[] args) {
Hello hello = new Hello();
for (int i = 0; i < 3; i++) {
new Thread(() ->{
try {
// hello.read();
hello.put("Hello");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
public void put(Object object) throws InterruptedException {
lock.writeLock().lock();
System.out.println("我來寫東西了++++");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " write " + object);
lock.writeLock().unlock();
}
public void read() throws InterruptedException {
lock.readLock().lock();
System.out.println("我來讀東西了++++");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " ++++++++++++");
lock.readLock().unlock();
}
}
偏向鎖、輕量級鎖、重量級鎖
偏向鎖: 它會偏向於第一個訪問鎖的線程,如果運行過程中,同步鎖只有一個線程訪問,不存在多線程競爭情況,線程不需要觸發同步鎖,系統會給線程加個偏向鎖。
輕量級鎖: 當有第二條線程加入競爭時,系統會把偏向鎖升級爲輕量鎖。
重量級鎖: 內置在java中被抽向爲監視器鎖(monitor)。調用成本非常高,包括系統調用引起的內核態與用戶切換、線程阻塞造成的線程切換等。(Syschronized
)