Synchronized和Lock區別+lambda表達式

這篇博客可能會比較長,想看結論可直接調到總結。

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的特點:

  1. Lock 上鎖和解鎖都需要開發者⼿動完成。
  2. 可以重複上鎖,上⼏把鎖就需要解⼏把鎖。
  3. 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的異同:

  1. ReentrantLock 就是對 synchronized 的升級,⽬的也是爲了實現線程同步。
  2. ReentrantLock 是⼀個類,synchronized 是⼀個關鍵字。
  3. ReentrantLock 是 JDK 實現,synchronized 是 JVM 實現。
  4. synchronized 可以⾃動釋放鎖,ReentrantLock 需要⼿動釋放。
  5. synchronized ⽆法判斷是否獲取到了鎖,Lock 可以判斷是否拿到了鎖。
  6. synchronized 拿不到鎖就會⼀直等待,Lock 不⼀定會⼀直等待
  7. synchronized 是⾮公平鎖,Lock 可以設置是否爲公平鎖。

公平鎖:很公平,排隊,當鎖沒有被佔⽤時,當前線程需要判斷隊列中是否有其他等待線程。
⾮公平鎖:不公平,插隊,當鎖沒有被佔⽤時,當前線程可以直接佔⽤,⽽不需要判斷當前隊列中是否
有等待線程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章