搞明白 線程
鎖
和多線程
系列
1.重入鎖
在類中有 synchronized 方法A 和 synchronized 方法B,並在A中調用B,就形成了重入鎖.獲得A的對象鎖,可以直接調用B,並不需要重新請求鎖權限.
package thread;
/** * @Author lyf * @Date 2018/11/18 0018 14:30 */
public class RepeatLock {
public synchronized void A(){
System.out.println("進入A...");
B();
}
public synchronized void B(){
System.out.println("進入B...");
}
public static void main(String[] args){
RepeatLock repeatLock = new RepeatLock();
for (int i = 0; i < 5; i++) {
new Thread(()->{repeatLock.A();}).start();
}
}
}

2.中斷鎖
使用synchronized修飾的方法,在阻塞狀態blocked時不能被外部打斷,除非jvm報錯.
使用ReentrantLock中的lockInterruptibly()處於阻塞狀態時, 可以被打斷.
package thread;
import java.util.concurrent.locks.ReentrantLock;
/** * @Author lyf * @Date 2018/11/18 0018 14:47 */
public class InterruptLock {
private ReentrantLock lock = new ReentrantLock();
public synchronized void A() {
try {
System.out.println(Thread.currentThread().getName() + "獲取鎖...");
while (true) {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "執行...");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
InterruptLock lock1 = new InterruptLock();
Thread t1 = new Thread(()->{lock1.A();});
t1.start();
Thread t2 = new Thread(()->{lock1.A();});
t2.start();
Thread.sleep(2000);
System.out.println(t1.getName() + ": "+t1.getState());// RUNNABLE 正在運行
System.out.println(t2.getName() + ": "+t2.getState());// BLOCKED 阻塞狀態
Thread.sleep(1000);
t2.interrupt();// 打斷t2線程
System.out.println(t2.getName() + ": "+t2.getState());// 依然處於BLOCKED 阻塞狀態
}
}

線程0獲取鎖后, 線程1進入阻塞等待狀態.對線程1調用interrupt()打斷線程,發現沒什么用!線程1還是在等待.
package thread;
import java.util.concurrent.locks.ReentrantLock;
/** * @Author lyf * @Date 2018/11/18 0018 14:47 */
public class InterruptLock {
private ReentrantLock lock = new ReentrantLock();
public void B() {
try {
lock.lockInterruptibly();
System.out.println(Thread.currentThread().getName() + "獲取鎖...");
while (true) {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "執行...");
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "被打斷...");
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
InterruptLock lock2 = new InterruptLock();
Thread t1 = new Thread(() -> { lock2.B(); });
t1.start();
Thread t2 = new Thread(() -> { lock2.B(); });
t2.start();
Thread.sleep(2000);
System.out.println(t1.getName() + ": "+t1.getState());// RUNNABLE 正在運行
System.out.println(t2.getName() + ": "+t2.getState());// WAITING 等待狀態
Thread.sleep(1000);
t2.interrupt();// 打斷t2線程
System.out.println(t2.getName() + ": "+t2.getState());// WAITING 狀態(線程延時)
}
}

很明顯線程1被打斷...
3.公平鎖
當多個線程處於阻塞狀態時,由系統隨機調度其中一個線程繼續執行,無法保證公平性.有可能某個線程永遠無法執行.ReentrantLock(true)可以創建公平鎖,保證每個阻塞的線程都可以被執行到.
首先,來看非公平鎖的情況,使用new ReentrantLock()
創建:
package thread;
import java.util.concurrent.locks.ReentrantLock;
/** * @Author lyf * @Date 2018/11/18 0018 18:46 */
public class FairLock {
private ReentrantLock lock = new ReentrantLock();
public synchronized void A() {
lock.lock();
System.out.println(Thread.currentThread().getName() + "獲得鎖...");
try {
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + "執行...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
FairLock fairLock = new FairLock();
new Thread(() -> {
fairLock.A();
}).start();// 啟動線程獲得鎖
for (int i = 0; i < 5; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "等待獲得鎖...");
fairLock.A();
}).start();
}
}
}

線程0獲得鎖,線程 4 3 5 2 1 依次進入等待狀態,在線程0執行完后,隨機選擇一個線程執行.
使用new ReentrantLock(true)
創建公平鎖
private ReentrantLock lock = new ReentrantLock(true);

線程0獲得鎖, 線程 1 2 5 4 3 依次進入等待狀態,在線程0執行完后,按照 順序 3 4 5 2 1 執行.
4.讀寫鎖
多個線程執行讀操作時,互不影響.但是讀寫互斥,寫寫互斥.
package thread;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/** * @Author lyf * @Date 2018/11/18 0018 10:30 */
public class MyReadWriteLock {
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private int num = 0;
public void readFile() {
rwl.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "執行讀操作...");
Thread.sleep((long) (Math.random() * 1000));
System.out.println(Thread.currentThread().getName() + "讀操作完成...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rwl.readLock().unlock();
}
}
public void writeFile() {
rwl.writeLock().lock();
System.out.println(Thread.currentThread().getName() + "執行寫操作...");
try {
num++;
Thread.sleep((long) (Math.random() * 1000));
System.out.println(Thread.currentThread().getName() + "寫操作完成...num: " + num);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rwl.writeLock().unlock();
}
}
public static void main(String[] args) {
MyReadWriteLock myReadWriteLock = new MyReadWriteLock();
for (int i = 0; i < 5; i++) {
new Thread(()->{myReadWriteLock.readFile();}).start();// 讀語句1
new Thread(()->{myReadWriteLock.readFile();}).start();// 讀語句2
new Thread(() -> { myReadWriteLock.writeFile(); }).start();// 寫語句
}
}
}

- 線程0 1 3讀操作同時開始,與線程5寫操作互斥
- 線程5寫操作與線程2寫操作互斥
5.自旋鎖
多線程下會造成線程處於阻塞狀態,不能執行其他操作.自旋鎖是一種非阻塞鎖,允許線程等待期間執行其他操作,直到獲得鎖.
package thread;
import java.util.concurrent.atomic.AtomicReference;
/** * @Author lyf * @Date 2018/11/18 0018 20:23 */
public class SpinLock {
// CAS操作
AtomicReference<Thread> runThread = new AtomicReference<>();
public void lock() {
Thread cur = Thread.currentThread();// 獲取當前線程
// 如果需要運行線程標志為null,也就是說現在沒有線程在執行,就執行當前線程
// 如果需要運行線程標志不為null,說明現在有線程在執行,就自己個兒執行自己的
while (!runThread.compareAndSet(null, cur)) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "在自旋中...");
}
try {
System.out.println(Thread.currentThread().getName() + "開始執行...");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "執行完畢...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void unlock() {
Thread cur = Thread.currentThread();
runThread.compareAndSet(cur, null);
}
public static void main(String[] args) {
SpinLock spinLock = new SpinLock();
Thread t1 = new Thread(()->{
spinLock.lock();
try {
Thread.sleep(2048);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLock.unlock();
});
Thread t2 = new Thread(()->spinLock.lock());
t1.start();
t2.start();
}
}

參考資料