Java併發編程學習系列一:線程與鎖

概念

什麼是線程和進程?

進程是程序的一次執行過程,是系統運行程序的基本單位,所以進程是動態的。系統運行一個程序便是一個進程從建立,運行到消亡的過程。java

在 Java 中,當咱們啓動 main 函數時其實就是啓動了一個 JVM 的進程,而 main 函數所在的線程就是這個進程中的一個線程,也稱主線程。nginx

線程與進程類似,但線程是一個比進程更小的執行單位。一個進程在其執行的過程當中能夠產生多個線程。與進程不一樣的是,同進程下的線程共享進程的方法區資源,但每一個線程有本身的程序計數器、虛擬機棧和本地方法棧,因此係統在產生一個線程,或是在各個線程之間作切換工做時,負擔要比進程小得多,也正由於如此,線程也被稱爲輕量級進程。web

進程和線程的區別是什麼?

  • 進程是運行中的程序,線程是進程的內部的一個執行序列;
  • 進程是資源分配的單元,線程是執行行單元;
  • 進程間切換代價大,線程間切換代價小;
  • 進程擁有資源多,線程擁有資源少;
  • 地址空間和其它資源:進程間相互獨立,同一進程的各線程間共享。某進程內的線程在其它進程不可見;
  • 通訊:進程間通訊 IPC,線程間能夠直接讀寫進程數據段(如全局變量)來進行通訊——須要進程同步和互斥手段的輔助,以保證數據的一致性;
  • 在多線程 OS 中,進程不是一個可執行的實體;

建立線程有幾種不一樣的方式?你喜歡哪種?爲何?

  1. 繼承 Thread 類(真正意義上的線程類),重寫 run 方法,其中 Thread 是 Runnable 接口的實現。
  2. 實現 Runnable 接口,並重寫裏面的 run 方法。
  3. 使用 Executor 框架建立線程池。Executor 框架是 juc 裏提供的線程池的實現。
  4. 實現 callable 接口,重寫 call 方法,有返回值。

通常狀況下使用 Runnable 接口,避免單繼承的侷限,一個類能夠繼承多個接口;適合於資源的共享。編程

注意:Java 本身開啓不了線程,在 Thread 類中執行 start 方法時,本質上調用的是本地方法 start0,即執行底層的 C++代碼,Java 沒法直接操做硬件。多線程

歸納的解釋下線程的幾種可用狀態。

  1. 新建( new ):新建立了一個線程對象。
  2. 可運行( runnable ):線程對象建立後,其餘線程(好比 main 線程)調用了該對象 的 start ()方法。該狀態的線程位於可運行線程池中,等待被線程調度選中,獲取 cpu 的使用權 。
  3. 運行( running ):可運行狀態( runnable )的線程得到了 cpu 時間片( timeslice ) ,執行程序代碼。
  4. 阻塞( block ):阻塞狀態是指線程由於某種緣由放棄了 cpu 使用權,也即讓出了 cpu timeslice ,暫時中止運行。直到線程進入可運行( runnable )狀態,纔有機會再次得到 cpu timeslice 轉到運行( running )狀態。阻塞的狀況分三種:
  • 等待阻塞:運行( running )的線程執行 o.wait ()方法, JVM 會把該線程放入等待隊列( waitting queue )中。
  • 同步阻塞:運行( running )的線程在獲取對象的同步鎖時,若該同步鎖被別的線程佔用,則 JVM 會把該線程放入鎖池( lock pool )中。
  • 其餘阻塞: 運行( running )的線程執行 Thread. sleep ( long ms )或 t . join ()方法,或者發出了 I / O 請求時, JVM 會把該線程置爲阻塞狀態。當 sleep ()狀態超時、 join ()等待線程終止或者超時、或者 I / O 處理完畢時,線程從新轉入可運行( runnable )狀態。
  1. 死亡( dead ):線程 run ()、 main () 方法執行結束,或者因異常退出了 run ()方法,則該線程結束生命週期。死亡的線程不可再次復生。

並行和併發有什麼區別?

  • 並行是指兩個或者多個事件在同一時刻發生;而併發是指兩個或多個事件在同一時間間隔發生。
  • 並行是在不一樣實體上的多個事件,併發是在同一實體上的多個事件。
  • 在一臺處理器上「同時」處理多個任務指的是併發,在多臺處理器上同時處理多個任務指的是並行。如 hadoop 分佈式集羣。
    併發編程的目標是充分的利用處理器的每個核,以達到最高的處理性能。

首先給出結論:「並行」概念是「併發」概念的一個子集。咱們常常據說這樣一個關鍵詞「多線程併發編程」,一個擁有多個線程或者進程的併發程序,但若是沒有多核處理器來執行這個程序,那麼就不能以並行方式來運行代碼。併發

若是某個系統支持兩個或者多個動做(Action)同時存在,那麼這個系統就是一個併發系統。app

若是某個系統支持兩個或者多個動做同時執行,那麼這個系統就是一個並行系統。框架

推薦閱讀:併發與並行的區別? - Limbo的回答 - 知乎分佈式

runnable 和 callable 有什麼區別?

  • 實現 Callable 接口的任務線程能返回執行結果;而實現 Runnable 接口的任務線程不能返回結果;
  • Callable 接口的 call()方法容許拋出異常;而 Runnable 接口的 run()方法的異常只能在內部消化,不能繼續上拋;

Lock鎖

傳統 synchronized

/**
 * 真正的多線程併發,公司中的開發,下降耦合性
 * 線程就是一個單獨的資源類,沒有任何附屬的操做!
 * 1.屬性、方法
 */

public class SychronizedDemo {

    public static void main(String[] args) {
        //併發,多線程操做同一個資源類,把資源類丟入線程
        Ticket ticket = new Ticket();

        //聲明線程使用lambda表達式,簡化匿名內部類的書寫
        new Thread(()->{
            for(int i=0;i<20;i++){
                ticket.sale();
            }
        },"A").start();
        new Thread(()->{
            for(int i=0;i<20;i++){
                ticket.sale();
            }
        },"B").start();
        new Thread(()->{
            for(int i=0;i<20;i++){
                ticket.sale();
            }
        },"C").start();
    }
}

class Ticket{
    private int num = 30;

    public synchronized void sale(){
        if (num > 0){
            System.out.println(Thread.currentThread().getName()+"賣出了一張票,剩餘:"+(--num));
        }
    }
}
複製代碼

執行結果爲:ide

A賣出了一張票,剩餘:29
A賣出了一張票,剩餘:28
A賣出了一張票,剩餘:27
A賣出了一張票,剩餘:26
A賣出了一張票,剩餘:25
B賣出了一張票,剩餘:24
B賣出了一張票,剩餘:23
B賣出了一張票,剩餘:22
B賣出了一張票,剩餘:21
B賣出了一張票,剩餘:20
B賣出了一張票,剩餘:19
B賣出了一張票,剩餘:18
B賣出了一張票,剩餘:17
B賣出了一張票,剩餘:16
B賣出了一張票,剩餘:15
B賣出了一張票,剩餘:14
B賣出了一張票,剩餘:13
B賣出了一張票,剩餘:12
B賣出了一張票,剩餘:11
B賣出了一張票,剩餘:10
B賣出了一張票,剩餘:9
B賣出了一張票,剩餘:8
B賣出了一張票,剩餘:7
B賣出了一張票,剩餘:6
B賣出了一張票,剩餘:5
A賣出了一張票,剩餘:4
A賣出了一張票,剩餘:3
A賣出了一張票,剩餘:2
A賣出了一張票,剩餘:1
A賣出了一張票,剩餘:0
複製代碼

上述代碼講述的是賣票的例子,總共有30張票,如今交由3個售票員進行售票,每次只能容許一個售票員來進行售票行爲,共用同一個票源。按照這樣的需求,咱們首先想到的是使用 synchronized 關鍵字,synchronized 關鍵字解決的是多個線程之間訪問資源的同步性,synchronized 關鍵字能夠保證被它修飾的方法或者代碼塊在任意時刻只能有一個線程執行。

Lock鎖

查看Lock鎖的使用模版:

咱們仍是基於上述代碼進行調整,主要就修改 Ticket 對象方法。

class Ticket{
    private int num = 30;

    Lock lock = new ReentrantLock();

    public void sale(){
        lock.lock();
        try {
            if (num > 0){
                System.out.println(Thread.currentThread().getName()+"賣出了一張票,剩餘:"+(--num));
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}
複製代碼

執行結果一致。

使用 Lock 鎖三步曲,一、聲明鎖;二、加鎖;3解鎖。

說說 synchronized 關鍵字和 Lock 類的區別

  • synchronized 是 Java 內置的關鍵字,Lock 是一個類;
  • synchronized 沒法判斷獲取鎖的狀態,Lock 能夠判斷是否獲取到了鎖;
  • synchronized 會自動釋放鎖,Lock 必須手動釋放鎖,若是不釋放,將會形成死鎖;
  • synchronized 若是有多個線程,線程1得到鎖執行時,其餘線程只能傻傻的等待,Lock 鎖不必定要等下去
  • synchronized 是可重入鎖,不可中斷,非公平鎖,Lock 可重入鎖,能夠判斷鎖,非公平鎖(能夠設置,自由度更高);
  • synchronized 適合鎖少許代碼的同步問題,Lock 適合鎖大量的代碼。
    public ReentrantLock() {
        this.sync = new ReentrantLock.NonfairSync();
    }

    public ReentrantLock(boolean var1) {
        this.sync = (ReentrantLock.Sync)(var1 ? new ReentrantLock.FairSync() : new ReentrantLock.NonfairSync());
    }
複製代碼

ReadWriteLock

該接口容許多個線程同時作讀操做,可是每次只能有一個線程來作寫操做。

public class ReadWriteLockDemo {

    public static void main(String[] args) {

        Mycache mycache = new Mycache();

        for (int i = 0; i < 5; i++) {
            final int index = i;
            new Thread(()->{
                mycache.put("hresh"+index);
            },String.valueOf(i)).start();
        }

        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                mycache.get();
            },String.valueOf(i)).start();
        }
    }
}

class Mycache{
    private volatile Map<String,Object> map = new HashMap<>();
    private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();

    public void put(String data){
        reentrantReadWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"準備寫入操做");
            map.put("name",data);
            System.out.println(Thread.currentThread().getName()+"寫入成功"+data);
        } finally {
            reentrantReadWriteLock.writeLock().unlock();
        }
    }

    public void get(){
        reentrantReadWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"讀取操做");
            System.out.println(map.get("name"));
            System.out.println(Thread.currentThread().getName()+"讀取成功");
        } finally {
            reentrantReadWriteLock.readLock().unlock();
        }
    }
}
複製代碼
0準備寫入操做
0寫入成功hresh0
2準備寫入操做
2寫入成功hresh2
3準備寫入操做
3寫入成功hresh3
1準備寫入操做
1寫入成功hresh1
4準備寫入操做
4寫入成功hresh4
2讀取操做
hresh4
2讀取成功
0讀取操做
1讀取操做
hresh4
1讀取成功
hresh4
0讀取成功
3讀取操做
hresh4
3讀取成功
4讀取操做
hresh4
4讀取成功
複製代碼

若是將 ReentrantReadWriteLock 改成 ReentrantLock 實現,觀察代碼運行結果,能夠發現使用了 ReentrantLock 的代碼,每次只能有一個線程作讀操做,而 ReentrantReadWriteLock 則是共享鎖,能夠容許多個線程來作讀操做。且讀寫操做互斥,必須寫完以後才能讀取。

Callable

注意:Callable 接口支持返回執行結果,此時須要調用 FutureTask.get()方法實現,此方法會阻塞主線程直到獲取‘未來’結果;當不調用此方法時,主線程不會阻塞!

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CallableImpl implements Callable<String{
    private String acceptStr;
    public CallableImpl(String acceptStr){
        this.acceptStr = acceptStr;
    }

    @Override
    public String call() throws Exception {
//        int i = 1/0;
        Thread.sleep(3000);
        System.out.println("hello : " + this.acceptStr);
        return this.acceptStr + " append some chars and return it!";
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<String> callable = new CallableImpl("my callable test!");
        FutureTask<String> task = new FutureTask<>(callable);
        long startTime = System.currentTimeMillis();

        //建立線程
        new Thread(task).start();
        // 調用get()阻塞主線程,反之,線程不會阻塞
        String result = task.get();

        long endTime = System.currentTimeMillis();
        System.out.println("hello : " + result);
        System.out.println("cast : " + (endTime - startTime) / 1000 + " second!");
    }
}
複製代碼

結果爲:

//執行結果爲:
hello : my callable test!
hello : my callable test! append some chars and return it!
cast : 3 second!

//若是註釋get()方法,結果變爲:
cast : 0 second!
hello : my callable test!
複製代碼

當取消 call 方法中關於 int i = 1/0;的註釋,程序結果變爲:

從結果中能夠看出,異常信息會向上拋出。

生產消費者問題

Sychronized,wait,notify

/**
 * @author hresh
 * @date 2020/2/16 21:19
 * @description
 * 線程之間的通訊問題:生產者和消費者問題
 * 傳統解決方法,Sychronized,wait,notify三者結合使用
 */

public class A {

    public static void main(String[] args) {
        Data data = new Data();

        new Thread(()->{
            for (int i=0;i<20;i++){
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();

        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
    }
}

class Data {
    private int num = 0;

    //判斷等待,業務,通知
    public synchronized void increment() throws InterruptedException {
        //注意這裏使用的是while判斷,而非if判斷,防止虛假喚醒
        while (num != 0){
            //等待
            this.wait();
        }
        num++;
        System.out.println(Thread.currentThread().getName()+"=>"+num);
        //通知其餘線程,我+1完畢了
        this.notifyAll();
    }

    public synchronized void decrement() throws InterruptedException {
        while (num == 0){
            this.wait();
        }
        num--;
        System.out.println(Thread.currentThread().getName()+"=>"+num);
        //通知其餘線程,我-1完畢了
        this.notifyAll();
    }
}
複製代碼

關於 wait 方法的判斷,必須使用 while 條件,官方文檔對此是這樣描述的。

Lock,await,signal

public class LockDemo {

    public static void main(String[] args) {
        Data2 data = new Data2();

        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data.increment();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data.decrement();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"B").start();

        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data.increment();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"C").start();

        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data.decrement();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }
}

class Data2 {
    private int num = 0;

    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    public void increment()  {
        lock.lock();
        try {
            while (num != 0){
                //等待
                condition.await();
            }
            num++;
            System.out.println(Thread.currentThread().getName()+"=>"+num);
            //通知其餘線程,我+1完畢了
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void decrement() {
        lock.lock();
        try {
            while (num == 0){
                //等待
                condition.await();
            }

            num--;
            System.out.println(Thread.currentThread().getName()+"=>"+num);
            //通知其餘線程,我-1完畢了
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}
複製代碼

從結果中能夠發現這種實現方式和 Synchronized 關鍵字效果一致,每當生產者生產完畢,都有一個消費者能夠獲取到來消費。此時若是有這麼一個需求:消費者B獲取生產者A的內容,D獲取C的內容,實現精準獲取,有序執行。採用 Synchronized 是沒法知足該需求的,可是 Lock 鎖有方法能夠實現。

定義3個線程,A執行完該B執行,B執行完該C執行,以後再從A開始。相似案例:好比說在生產線中:下單-》支付-》交易-》物流

public class NewLockDemo {
    public static void main(String[] args) {
        Data3 data3 = new Data3();

        new Thread(()->{
            for(int i=0;i<10;i++){
                try {
                    data3.printA();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(()->{
            for(int i=0;i<10;i++){
                try {
                    data3.printB();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"B").start();

        new Thread(()->{
            for(int i=0;i<10;i++){
                try {
                    data3.printC();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
    }
}

class Data3{
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    private int num = 1;

    public void printA(){
        lock.lock();

        try {
            while (num != 1){
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName()+"AAAAA");
            num = 2;
            condition2.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void printB(){
        lock.lock();

        try {
            while (num != 2){
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName()+"BBBBBBBB");
            num = 3;
            condition3.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void printC(){
        lock.lock();

        try {
            while (num != 3){
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName()+"CCCCCCCC");
            num = 1;
            condition1.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}
複製代碼

經過聲明3個 Condition 對象,每次調用方法,指定某個 Condition 等待,而後釋放某個準確的 Condition。

8鎖問題

Synchronized 鎖實例對象

以下案例,phone 對象調用 sendSms 和 call 方法,先執行哪一個方法,就意味着該方法獲取了 phone 對象的鎖,另一個方法就必須等待鎖被釋放後,才能夠執行。

/**
 * @author hresh
 * @date 2020/2/16 22:02
 * @description
 * 8鎖,關於鎖的8個問題
 * 1.標準狀況下,先打印發短信仍是打電話?答:一、發短信;二、打電話
 * 2.sendSms延時3m,先打印哪一個?答:一、發短信;二、打電話
 */

public class Test1 {

    public static void main(String[] args) {

        Phone phone = new Phone();

        new Thread(()->{
            phone.sendSms();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone.call();
        },"B").start();
    }
}

class Phone{

    // Synchronized 鎖的對象是方法的調用者,即new出來的對象
    //如下兩個方法共用同一把鎖
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("發短信");
    }

    public synchronized void call(){
        System.out.println("打電話");
    }
}
複製代碼

當對象中除了同步方法外,還有一個普通方法,執行順序又將變成什麼樣子?

//3.一個對象,添加一個普通方法,先打印hello仍是發短信?
public class Test2 {

    public static void main(String[] args) {
        Phone2 phone1 = new Phone2();

        new Thread(()->{
            phone1.sendSms();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone1.hello();
        },"B").start();
    }
}

class Phone2{

    //Sychronized鎖的對象是方法的調用者
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("發短信");
    }

    public void hello(){
        System.out.println("hello");
    }
}
複製代碼

執行結果爲:

hello
發短信
複製代碼

在第一個案例中,咱們說過 Synchronized 鎖的對象是 phone 對象,這是針對同步方法而言,可是普通方法的調用並不須要獲取鎖,因此當同步方法在延時等待時,普通方法就能夠正常執行。

當鎖對象有兩個時,分別調用一個方法,結果又是怎樣?

//兩個對象,分別調用發短信和打電話,先打印打電話
public class Test2 {

    public static void main(String[] args) {
        //兩個對象
        Phone2 phone1 = new Phone2();
        Phone2 phone2 = new Phone2();

        new Thread(()->{
            phone1.sendSms();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone2.call();
        },"B").start();
}

class Phone2{

    //Sychronized鎖的對象是方法的調用者
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("發短信");
    }

    public synchronized void call(){
        System.out.println("打電話");
    }
}
複製代碼

執行結果:

打電話
發短信
複製代碼

由於有兩個鎖對象,因此你們各自都有一把鎖,不須要等待鎖釋放,sendSms 方法由於要延遲等待,因此就是 call 方法先執行。

Synchronized 鎖Class對象

被 static 修飾的方法爲靜態方法,隨着類的加載而加載,只加載一次,不須要實例化對象,能夠經過類直接進行調用,因此此時鎖的對象是 Phone3.class。

//5.一個對象,兩個方法都聲明爲靜態的,分別調用發短信和打電話,先打印發短信
public class Test3 {

    public static void main(String[] args) {
        Phone3 phone1 = new Phone3();

        new Thread(()->{
            phone1.sendSms();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone1.call();
        },"B").start();

    }
}

class Phone3{

    //Sychronized鎖的對象是方法的調用者
    //static 靜態方法
    //類一加載就有了!Class模版
    public static synchronized void sendSms(){

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("發短信");
    }

    public static synchronized void call(){
        System.out.println("打電話");
    }

}
複製代碼

若是咱們聲明兩個對象,分別調用這兩個方法,結果又是怎樣的呢?

//6.兩個對象,一樣都是靜態的,先打印發短信,由於雖然對象不一樣,可是鎖的是同一個類模版
public class Test3 {

    public static void main(String[] args) {
        Phone3 phone1 = new Phone3();
        Phone3 phone2 = new Phone3();

        new Thread(()->{
            phone1.sendSms();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone2.call();
        },"B").start();

    }
}
複製代碼

結果與上面同樣,都是先打印發短信。緣由在於 Phone.class 只有一份,關於它的鎖誰先拿到就先執行,與 new 多少個對象無關。

Synchronized 多鎖

當代碼中存在多個鎖時,也即上面提到的兩種鎖,一種是實例對象鎖,一種是類對象鎖。

//7.一個對象,一個靜態同步,一個普通同步,先打印打電話
public class Test4 {

    public static void main(String[] args) {
        Phone4 phone1 = new Phone4();

        new Thread(()->{
            phone1.sendSms();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone1.call();
        },"B").start();

    }
}

class Phone4{

    //靜態同步方法
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("發短信");
    }

    public synchronized void call(){
        System.out.println("打電話");
    }
}
複製代碼

結果中先打印打電話,緣由在於這兩個方法須要拿到的鎖不同,因此並不存在等待鎖釋放。

即便聲明兩個對象,一個調用靜態同步方法,一個調用普通同步方法,結果與上述一致。

//8.兩個對象,一個靜態同步,一個普通同步,先打印打電話
public class Test4 {

    public static void main(String[] args) {
        Phone4 phone1 = new Phone4();
        Phone4 phone2 = new Phone4();

        new Thread(()->{
            phone1.sendSms();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone2.call();
        },"B").start();

    }
}
複製代碼

Synchronized 鎖代碼塊

Synchronized 關鍵字還能夠用來修飾同步代碼塊,在某些狀況下,咱們可能只須要同步一部分代碼,不必對整個方法進行同步操做,此時咱們可使用同步代碼塊的方式對須要同步的代碼進行包裹,這樣就無需對整個方法進行同步操做了,同步代碼塊的使用示例以下:

//this,當前實例對象鎖
synchronized(this){
    for(int j=0;j<1000000;j++){
        i++;
    }
}

//class對象鎖
synchronized(AccountingSync.class){
    for(int j=0;j<1000000;j++){
        i++;
    }
}
複製代碼

咱們可使用 this 對象(表明當前實例)或者當前類的 class 對象做爲鎖。如下案例來比較這三種方式的運行結果:

//9.一個對象,一個靜態同步,一個普通同步方法,一個同步代碼塊,先打印打電話,最後發短信
public class Test5 {

    public static void main(String[] args) {
        Phone5 phone1 = new Phone5();

        new Thread(()->{
            phone1.sendSms();
        },"A").start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone1.call();
        },"B").start();

        new Thread(()->{
            phone1.test();
        },"C").start();
    }

}

class Phone5{

    //靜態同步方法
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("發短信");
    }

    public synchronized void call(){
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("打電話");
    }

    public void test(){
        synchronized (this){
            System.out.println("synchronized code");
        }
    }
}
複製代碼

執行結果爲:

打電話
synchronized code
發短信
複製代碼
相關文章
相關標籤/搜索