在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