這篇博客可能會比較長,想看結論可直接調到總結。
1.synchronized
synchronized大家應該都很熟悉,是java中一個常用的關鍵字。(而lock是JUC中的接口,後面會講到)
這裏用一個死鎖的demo熟悉一下synchronized的用法。
public class DeadLockRunable implements Runnable{
public int num;
//資源(同時又兩隻筷子纔可以喫飯)
private static Chopsticks chopsticks1 = new Chopsticks();
private static Chopsticks chopsticks2 = new Chopsticks();
/**
* num = 1 拿到 chopsticks1,等待 chopsticks2
* num = 2 拿到 chopsticks2,等待 chopsticks1
*/
@Override
public void run() {
if (num == 1){
System.out.println(Thread.currentThread().getName()+"拿到了筷子1,等待筷子2");
//鎖定資源-chopsticks1
synchronized (chopsticks1){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (chopsticks2){
System.out.println(Thread.currentThread().getName()+"用餐完畢!");
}
}
}
if (num == 2){
System.out.println(Thread.currentThread().getName()+"拿到了筷子2,等待筷子1");
//鎖定資源-chopsticks2
synchronized (chopsticks2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (chopsticks1){
System.out.println(Thread.currentThread().getName()+"用餐完畢!");
}
}
}
}
}
test類
public class MyTest {
public static void main(String[] args) {
DeadLockRunable deadLockRunable1 = new DeadLockRunable();
deadLockRunable1.num = 1;
DeadLockRunable deadLockRunable2 = new DeadLockRunable();
deadLockRunable2.num = 2;
new Thread(deadLockRunable1,"李雷").start();
new Thread(deadLockRunable2,"韓梅梅").start();
}
}
運行會發現程序不會停止,兩個線程相互等待對方鎖住的資源,故處於死鎖的狀態。要注意的是:synchronized鎖的對象一定要滿足一定條件纔會生效:synchronized鎖住的對象一定是多個線程共享且唯一不變的元素,即內存中獨一份。(個人理解)
當然,爲了避免以上死鎖,也很簡單:
test類稍作修改,既保證兩個線程拿資源不衝突就可:
public class MyTest {
public static void main(String[] args) {
DeadLockRunable deadLockRunable1 = new DeadLockRunable();
deadLockRunable1.num = 1;
DeadLockRunable deadLockRunable2 = new DeadLockRunable();
deadLockRunable2.num = 2;
new Thread(deadLockRunable1,"李雷").start();
try {
TimeUnit.MILLISECONDS.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(deadLockRunable2,"韓梅梅").start();
}
}
2.Lock之前記一下lambda 表達式
1中的代碼通過lambda 表達式可以直接寫在一個類中的:
public class MyTest {
public static void main(String[] args) {
new Thread(()->{
for(int i=0;i<100;i++) {
System.out.println("+++++++++++Runnable");
}
}).start();
new Thread(()->{
for(int i=0;i<100;i++) {
System.out.println("===========Runnable");
}
}).start();
}
}
這種方式寫出來的代碼既優雅又簡潔,實現了任務和業務邏輯的解耦合。
3.Lock
Lock是JUC(java.util.concurrent包裏的)java併發工具包。
Lock 使⽤頻率最⾼的實現類是 ReentrantLock(重⼊鎖),可以重複上鎖。
做一個小demo熟悉一下ReentrantLock:
public class ReentrantLockTest {
public static void main(String[] args) {
Acount acount = new Acount();
new Thread(()->{
acount.lock();
}).start();
new Thread(()->{
acount.lock();
}).start();
}
}
class Acount{
private static int num;
private Lock lock = new ReentrantLock();
public void lock(){
lock.lock();
num++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"是第"+ num + "位訪客");
lock.unlock();
}
}
輸出結果是正確的,說明lock有效果的。你把lock()那裏去掉會發現,結果兩個線程都是第一位訪客。因爲jvm內存模型的關係。也有可能出現併發修改錯誤(concurrentModifiedException).
Lock的特點:
- Lock 上鎖和解鎖都需要開發者⼿動完成。
- 可以重複上鎖,上⼏把鎖就需要解⼏把鎖。
- ReentrantLock 除了可以重⼊之外,還有⼀個可以中斷的特點:可中斷是指某個線程在等待獲取鎖的過程中可以主動過終⽌線程。
public class ReentrantLockTest {
public static void main(String[] args) {
StopLock stopLock = new StopLock();
Thread t1 = new Thread(()->{
stopLock.service();
},"A");
Thread t2 =new Thread(()->{
stopLock.service();
},"B");
t1.start();
t2.start();
try {
TimeUnit.SECONDS.sleep(1);
t2.interrupt();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class StopLock {
private ReentrantLock reentrantLock = new ReentrantLock();
public void service() {
try {
reentrantLock.lockInterruptibly();
System.out.println(Thread.currentThread().getName() + "get lock");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} finally {
reentrantLock.unlock();
}
}
}
運行可發現,第二個線程過了一秒還沒有拿到鎖,就主動中斷了線程,拋出InterruptedException異常。
4.總結
Synchronized和Lock的異同:
- ReentrantLock 就是對 synchronized 的升級,⽬的也是爲了實現線程同步。
- ReentrantLock 是⼀個類,synchronized 是⼀個關鍵字。
- ReentrantLock 是 JDK 實現,synchronized 是 JVM 實現。
- synchronized 可以⾃動釋放鎖,ReentrantLock 需要⼿動釋放。
- synchronized ⽆法判斷是否獲取到了鎖,Lock 可以判斷是否拿到了鎖。
- synchronized 拿不到鎖就會⼀直等待,Lock 不⼀定會⼀直等待
- synchronized 是⾮公平鎖,Lock 可以設置是否爲公平鎖。
公平鎖:很公平,排隊,當鎖沒有被佔⽤時,當前線程需要判斷隊列中是否有其他等待線程。
⾮公平鎖:不公平,插隊,當鎖沒有被佔⽤時,當前線程可以直接佔⽤,⽽不需要判斷當前隊列中是否
有等待線程。