在Java中String是一種特殊的類型存在,在jdk中String在建立後是共享常量池的,即便在jdk1.8以後實現有所不一樣,可是功能仍是差很少的。java
藉助這個特色咱們可使用String來做同步的鎖,好比更新用戶信息的時候,可使用用戶的名稱做爲同步鎖,這樣不一樣的用戶就可使用不一樣的鎖,提高併發性能。這個特色擴展開來適當的場景就很是之多了。redis
只不過正由於String的特殊性,java還包含了更多的與字符串相關的工具類,如StringBuffer、StringBuilder等。並且字符串映射的值是常量,可是String自己是能夠new出來相似一個變量使用的。這些狀況就會影響線程的同步了。併發
針對這些狀況逐一測試一下。app
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
由上引伸到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