1. 程式人生 > >java中synchronized關鍵字的認識&記錄

java中synchronized關鍵字的認識&記錄

背景

在工作中經常會遇到需要做執行緒同步處理的場景,但由於一直對執行緒同步是一知半解,沒有很系統的去了解過,於是最近終於踩到了由於synchronized同步應用不當導致app頻繁ANR無響應的坑了,下面就這次踩坑來對synchronized執行緒同步來做一個較全面的瞭解,以此說明細節也加深印象。

app發生ANR的產生場景

我們先直接看發生anr的示意函式:

//Tab.java

/**
 * 該方法在主執行緒中呼叫,並且使用了synchronized同步關鍵字,且處理的邏輯較多。
 */
public synchronized boolean goBackOrForward(OffsetToPosInfo info) {
    //...
//處理頁面向前和回退時,一些狀態的維護和線場的儲存等 //... } /** * 該方法主要工作是在特有的子執行緒中生成截圖Bitmap,以便Tab工作管理員中展示時獲取。 */ void updateCaptureFromBlob(byte[] blob) { Bitmap capture = mCapture; synchronized (Tab.this) { //注意同步程式碼塊 Options ops = new Options(); ops.inMutable = true; ops.inPreferredConfig = Bitmap.Config.RGB_565; //此處為耗時操作
Bitmap bitmap = BitmapFactory.decodeByteArray(blob, 0, blob.length, ops); //... 其他邏輯 } }

分析原因並重現問題:
分析anr的trace檔案堆疊資訊時發現當時一直阻塞在函式goBackOrForward()處且沒有更多的堆疊資訊,但有如下提示:awaiting to lock <0x08308eb5> held by thread 54。結合函式一看便知是當前執行緒(主執行緒)在等待其他執行緒(子執行緒)對synchronized鎖的釋放,於是順著找到了tid=54的子執行緒,並檢視堆疊資訊於是找到了該執行緒正在執行函式updateCaptureFromBlob()的同步程式碼塊,阻塞在BitmapFactory.decodeByteArray()處。

總結原因為:主執行緒的ui操作依賴子執行緒耗時函式中的synchronized同步鎖,最終發生了ANR無響應。

synchronized關鍵字的相關概念

注意:此處均為網上查詢的資料,以備後續查詢。

1、內建鎖
synchronized關鍵字涉及到鎖的概念,需要了解一些相關鎖的知識。

java的內建鎖:每個java物件都可以用做一個實現同步的鎖,這些鎖成為內建鎖。執行緒進入同步程式碼塊或方法的時候會自動獲得該鎖,在退出同步程式碼塊或方法時會釋放該鎖。獲得內建鎖的唯一途徑就是進入這個鎖的保護的同步程式碼塊或方法。

java內建鎖是一個互斥鎖,這就是意味著最多隻有一個執行緒能夠獲得該鎖,當執行緒A嘗試去獲得執行緒B持有的內建鎖時,執行緒A必須等待或者阻塞,直到執行緒B釋放這個鎖,如果B執行緒不釋放這個鎖,那麼A執行緒將永遠等待下去。

**java的物件鎖和類鎖:**java的物件鎖和類鎖在鎖的概念上基本上和內建鎖是一致的,但是,兩個鎖實際是有很大的區別的,物件鎖是用於物件例項方法,或者一個物件例項上的,類鎖是用於類的靜態方法或者一個類的class物件上的。

2、synchronized關鍵字的基本介紹

synchronized可以修飾方法,也可以修飾一個程式碼塊。

物件鎖的使用:

    /**
     * synchronized修飾方法名的物件鎖
     */
    private synchronized void test1() {
        //do something...
    }


    /**
     * synchronized修飾程式碼塊的物件鎖
     */
    private void test2() {
        //do something1...

        synchronized (this) {
            //do something2...
        }

        //do something3...
    }

類鎖的使用:

    /**
     * synchronized修飾方法名的類鎖(靜態方法)
     */
    private synchronized static void test3() {
        //do something...
    }


    /**
     * synchronized修飾程式碼塊的類鎖(非靜態方法)
     */
    private static void test4() {
        //do something1...

        synchronized (MainActivity.class) {
            //do something2...
        }

        //do something3...
    }

3、物件鎖和類鎖修飾的方法之間執行緒同步的測試

對於在物件鎖相互之間和類鎖相互之間進行執行緒同步我們很好理解,但是對於在物件鎖和類鎖之間進行執行緒同步時,會是什麼樣的結果,表示不能確定,於是寫demo驗證了一下:

    public class Test {

        /**
         * 使用synchronized修飾程式碼塊來實現物件鎖
         */
        public void test1() {
            synchronized (this) {
                for (int i = 0; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName() + ": " + i);

                    try {
                        Thread.sleep(1000);
                    } catch (Exception e) {
                    }
                }
            }
        }

        /**
         * 使用synchronized修飾靜態方法來實現類鎖
         */
        public synchronized static void test2() {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + ": " + i);

                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                }
            }
        }


    public static void main(String arg[]){

        final Test test = new Test();
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                test.test1();
            }
        }, "test1");

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                test.test2();
            }
        }, "test2");

        thread1.start();
        thread2.start();
    }

    }


輸出結果:
test1: 0
test2: 0
test1: 1
test2: 1
test1: 2
test2: 2
test1: 3
test2: 3
test1: 4
test2: 4 

結果表明,執行緒1和執行緒2在交替執行。物件鎖和類鎖的修飾的函式間進行執行緒同步時並不能達到我們預期的同步要求。這證明了類鎖和物件鎖是兩個不一樣的鎖,控制著不同的區域,它們互不干擾。執行緒獲得物件鎖的同時,也可以獲得該類鎖。

synchronized修飾程式碼塊的用處

既然有了synchronized修飾方法,為什麼還需要synchronized來修飾程式碼塊呢?java這樣如此設計肯定是為了避免或解決什麼問題。
1、便於降低同步程式碼的粒度,以此來提高效能。舉例:

        public synchronized void test3() {

            /**
             * 需要同步處理的關鍵程式碼
             */
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }

            /**
             * 這裡特別耗時,但不要求同步處理
             */
            for (int i = 0; i < 1000000; i++) {
                //...do something
            }
        }

        public void test4() {
            /**
             * 需要同步處理的關鍵程式碼
             */
            synchronized (this) {
                for (int i = 0; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName() + ": " + i);
                }
            }

            /**
             * 這裡特別耗時,但不要求同步處理
             */
            for (int i = 0; i < 1000000; i++) {
                //...do something
            }
        }

方法test4()的效率明顯高於方法test3(),因為test4的同步粒度更小,需要等待的時間更短。

2、便於實現區域性功能的同步。舉例:

        private final Object mLock = new Object();

        public synchronized void test5() {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }

        public void test6() {
            synchronized (mLock) {
                for (int i = 0; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName() + ": " + i);
                }
            }
        }

        public void test7() {
            synchronized (mLock) {
                for (int i = 0; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName() + ": " + i);
                }
            }
        }

方法test6()和test7()線上程間同步處理被阻塞時,函式test5()仍然可以進去。這是因為使用了不同的所物件,這對於僅僅實現類的區域性同步時有著更高的處理效率。

總結

回到最開始的ANR的例子這裡由於兩個方法goBackOrForward()和updateCaptureFromBlob同時使用了同一個同步物件鎖所致,解決辦法是使子執行緒處理函式updateCaptureFromBlob()使用一個區域性鎖來解耦主執行緒對其的依賴。如下所示:

private final Object mCaptureLock = new Object();

void updateCaptureFromBlob(byte[] blob) {
    Bitmap capture = mCapture;

    synchronized (mCaptureLock) { //使用一個僅處理該邏輯的才使用的一個物件鎖

        Options ops = new Options();
        ops.inMutable = true;
        ops.inPreferredConfig = Bitmap.Config.RGB_565;

        //此處為耗時操作
        Bitmap bitmap = BitmapFactory.decodeByteArray(blob, 0,
                        blob.length, ops);

        //... 其他邏輯 
    }
}

相關推薦

javasynchronized關鍵字認識&記錄

背景 在工作中經常會遇到需要做執行緒同步處理的場景,但由於一直對執行緒同步是一知半解,沒有很系統的去了解過,於是最近終於踩到了由於synchronized同步應用不當導致app頻繁ANR無響應的坑了,下面就這次踩坑來對synchronized執行緒同步來做一個

Javasynchronized關鍵字理解

監視器 pre 定義 exc 執行 zed 三種 gen 好記性不如爛筆頭 好記性不如爛筆頭~~ 並發編程中synchronized關鍵字的地位很重要,很多人都稱它為重量級鎖。利用synchronized實現同步的基礎:Java中每一個對象都可以作為鎖。具體表現為以下三種形

Javasynchronized關鍵字使用實踐

1、synchronized修飾類的普通方法 package main.thread; /** * Created by leboop on 2018/11/18. * 測試synchronized關鍵字使用 */ public class SynchronizedClass {

Javasynchronized關鍵字你知道多少

1.什麼是synchronized 我們將其理解為同步鎖,可以實現共享資源的同步訪問,解決執行緒併發的安全問題。synchronize翻譯成中文:同步,使同步。synchronized:已同步。 1.1 怎麼使用的 修飾例項方法,作用於當前物件例項加鎖,進入同步程式碼前要獲得當前物件例項的鎖 修飾

Java關鍵字synchronized

1. 介紹 在Java併發系列的文章中,這個是第二篇文章。在前面的一篇文章中,我們學習了Java中的Executor池和Excutors的各種類別。 在這篇文章中,我們會學習synchronized關鍵字以及我們在多執行緒的環境中如何使用。 2. 什麼是同步? 在一個多執行緒的環境中,多個執行緒同時訪

JAVAthis關鍵字的用法

blog http tro font return his 局部變量 .com str this關鍵字主要有三個應用: 1.調用本類中的屬性,也就是類的成員變量; 2.調用本類中的其他方法; 3.調用本類中的其他構造方法,調用時候要放在構造方法的首行。 * this關鍵

就是要你懂Javavolatile關鍵字實現原理

stub string home 技術分享 訪問速度 get 地址傳遞 code 緩沖 原文地址http://www.cnblogs.com/xrq730/p/7048693.html,轉載請註明出處,謝謝 前言 我們知道volatile關鍵字的作用是保證變量在多線程之

javavolatile關鍵字的含義

能夠 system 內存區域 退出 tro 技術 2.4 虛擬機 們的 在java線程並發處理中,有一個關鍵字volatile的使用目前存在很大的混淆,以為使用這個關鍵字,在進行多線程並發處理的時候就可以萬事大吉。 Java語言是支持多線程的,為了解決線程並發的問題,在語

Javastatic關鍵字用法總結

副本 大括號 跟著 rac clas main 靜態成員變量 abstract 全局變量 1. 靜態方法 通常,在一個類中定義一個方法為static,那就是說,無需本類的對象即可調用此方法 聲明為static的方法有以下幾條限制: · 它們僅能調用其他的sta

Javainstanceof關鍵字的用法總結

animal copy false 運算 erl 一個 strong 以及 繼承 instanceof是Java的一個二元操作符,和==,>,<是同一類東東。由於它是由字母組成的,所以也是Java的保留關鍵字。它的作用是測試它左邊的對象是否是它右邊的類的實例,

[轉] javavolatile關鍵字的含義

讀取 add 由於 tar 並不是 ges 內部 post 計數 在java線程並發處理中,有一個關鍵字volatile的使用目前存在很大的混淆,以為使用這個關鍵字,在進行多線程並發處理的時候就可以萬事大吉。 Java語言是支持多線程的,為了解決線程並發的問題,在

Javainstanceof關鍵字的用法

m60 cin .com 關鍵字 adr www. zdb kms http f72o1u前盞彜渦踴促http://shequ.docin.com/mym827424fzhy9晨撈晨詡鷗淹http://www.docin.com/rww41025xh82bc僭攀潭倜歡稱ht

Javavolatile關鍵字實現原理

三級 poll 解讀 內存屏障 就會 主存 發生 調用 獲得 原文地址http://www.cnblogs.com/xrq730/p/7048693.html,轉載請註明出處,謝謝 前言 我們知道volatile關鍵字的作用是保證變量在多線程之間的可見性,它是ja

JavaSynchronized的用法

turn 效果 互斥 obj href sta dem data 總結 《編程思想之多線程與多進程(1)——以操作系統的角度述說線程與進程》一文詳細講述了線程、進程的關系及在操作系統中的表現,這是多線程學習必須了解的基礎。本文將接著講一下Java線程同步中的一個

隨筆② Java關鍵字 --- final關鍵字

初始 編譯 this 集合 div 緩存 nal ext 環境 一:final關鍵字 ① final變量:凡是對成員變量或者本地變量(在方法中的或者代碼塊中的變量稱為本地變量)聲明為final的都叫作final變量。final變量經常和static關鍵字一起使用,作為常量。

隨筆14 java關鍵字

this div .cn ges -1 style class clas ron 一:java中的關鍵字列表 二:super關鍵字 super關鍵字在子類內部使用,代表父類對象。 訪問父類的屬性 super.屬性名 訪問父類的方法 super.方法名() 子類構

Javathis關鍵字的使用

成員變量 用法 表示 當前 重載 說明 可讀性 舉例 調用構造 說明:this用於指向調用該方法的當前對象。 用法: 1. this.成員變量 ------ 表示訪問當前對象的成員變量 2. this() ------

javafinal關鍵字

成員變量 子類 void color 需要 sta new 根據 基本用法 在Java中,final關鍵字可以用來修飾類、方法和變量(包括成員變量和局部變量)。下面就從這三個方面來了解一下final關鍵字的基本用法。 1.修飾類   當用final修飾一個類時,表明這個類

Java關鍵字 transient

puts amp 存儲 style object code pin map 這一 在討論transient之前,有必要先搞清楚Java中序列化的含義; Java中對象的序列化指的是將對象轉換成以字節序列的形式來表示,這些字節序列包含了對象的數據和信息,一個序列化後的對象可以

Java 關鍵字和保留字

width interface continue rac borde ctf got break order 關鍵字: Java 語言中已經事先定義好了的,有著特殊含義和用途 訪問控制 類、方法和變量修飾符 程序控制 異常處理 包相關 基本類型 變量引用 publ