Java中String作爲synchronized同步鎖使用詳解

Java中使用String做同步鎖

在Java中String是一種特殊的類型存在,在jdk中String在建立後是共享常量池的,即便在jdk1.8以後實現有所不一樣,可是功能仍是差很少的。java

藉助這個特色咱們可使用String來做同步的鎖,好比更新用戶信息的時候,可使用用戶的名稱做爲同步鎖,這樣不一樣的用戶就可使用不一樣的鎖,提高併發性能。這個特色擴展開來適當的場景就很是之多了。redis

只不過正由於String的特殊性,java還包含了更多的與字符串相關的工具類,如StringBuffer、StringBuilder等。並且字符串映射的值是常量,可是String自己是能夠new出來相似一個變量使用的。這些狀況就會影響線程的同步了。併發

針對這些狀況逐一測試一下。app

使用new String()做一下測試

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class TestStringSync {
    private static Integer CNT = 0;
    
    public static void main(String[] args) {
        final String lock = new String(ObjectId.get().toString());
        run(lock);
    }

    private static void run(String lock) {
        final Integer threadNum = 10;
        final CyclicBarrier cb = new CyclicBarrier(threadNum, new Runnable() {
            
            public void run() {
                System.out.println("threadNum : " + threadNum);
            }
        });
        
        for(int i = 0; i< threadNum; i++) {
            String tmpLock = new String(lock);
            new TestThread(cb, tmpLock.toString()).start();
        }
    }

    static class TestThread extends Thread {
        private CyclicBarrier cbLock;
        private String lock;
        
        public TestThread(CyclicBarrier cbLock, String lock) {
            this.cbLock = cbLock;
            this.lock = lock;
        }
        public void run() {
            try {
                cbLock.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
            
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            
            synchronized(lock) { //這裏直接使用String對象自己做爲鎖
                CNT = CNT+1;
                System.out.println("Value:" + CNT);
            }           
        }
    }
    
}

輸出的結果分佈式

threadNum : 10
Value:2
Value:2
Value:2
Value:2
Value:4
Value:5
Value:5
Value:4
Value:4
Value:4

從結果能夠看出,每一個線程建立前使用new String(lock)會產生不一樣的鎖,形成線程同步失敗。因此在使用的時候要特別注意這點,new String(lock)是會產生不一樣的對象,他們所指向的對象鎖是不一樣的。ide

StringBulider和StringBuffer的問題

由上引伸到StringBuilder和StringBuffer,這也是使用字符串做爲同步鎖須要注意的問題。好比某些場景下須要對字符串拼接後做爲鎖。好比:用戶名+機構名:工具

StringBuilder tmpLock = new StringBuilder();
tmpLock.append("user name");
tmpLock.append("org name");
for(int i = 0; i< threadNum; i++) {
    new TestThread(cb, tmpLock.toString()).start();
}

運行結果性能

threadNum : 10
Value:2
Value:2
Value:2
Value:3
Value:2
Value:3
Value:2
Value:3
Value:2
Value:2

可見,這個鎖仍是不行。緣由是StringBuiler的toString方法中返回的是new String,代碼以下:測試

@Override
    public String toString() {
        // Create a copy, don't share the array
        return new String(value, 0, count);
    }

這就致使線程拿到的仍是不一樣的字符串對象。ui

解決方法

針對上面舉的例子能夠發現,使用String做爲同步鎖必須注意產生不一樣對象的問題,必須保證線程拿到的是同一個String對象。作法最簡單的就是使用同一個String對象,但這個有時很難保證。特別是咱們不少的時候代碼是分佈式環境下的。

好比,咱們將用戶名存在了redis裏,線程每次同步的時候去redis裏取一下數據,這樣就頗有可能致使產生新的String對象。這個時候就得使用intern()方法。上面的代碼修改成:

synchronized(lock.intern()) {
    CNT = CNT+1;
    System.out.println("Value:" + CNT);
}

這樣就是直接獲取的是字符串的值自己,而不是取的String的對象,以此保證同一個字符串拿到的是同一個String對象,天然在同一個進程中就是同一個對象鎖了。

測試結果

threadNum : 10
Value:1
Value:2
Value:3
Value:4
Value:5
Value:6
Value:7
Value:8
Value:9
Value:10
相關文章
相關標籤/搜索