• synchronized(string)


    1. 前言

    String是Java中的一种特殊类型,String在创建后存入字符串常量池
    利用这个特征,可以使用String作为同步锁。例如,在更新用户信息时,可以使用用户的名称作为同步锁,不同的用户可以使用不同的锁来提高并发性能。这个特征扩展适当的场景非常多。

    2. 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);
    			}			
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    threadNum : 10
    Value:2
    Value:2
    Value:2
    Value:2
    Value:4
    Value:5
    Value:5
    Value:4
    Value:4
    Value:4
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    从结果可以看出,每个线程创建前使用new String(lock)会产生不同的锁,造成线程同步失败。所以在使用的时候要特别注意这点,new String(lock)是会产生不同的对象,他们所指向的对象锁是不同的。

    3. StringBulider和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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    threadNum : 10
    Value:2
    Value:2
    Value:2
    Value:3
    Value:2
    Value:3
    Value:2
    Value:3
    Value:2
    Value:2
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    可见,这个锁还是不行。原因是StringBuiler的toString方法中返回的是new String,代码如下:

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

    这就导致线程拿到的还是不同的字符串对象。

    4. 解决方法

    针对上面举的例子可以发现,使用String作为同步锁必须注意产生不同对象的问题,必须保证线程拿到的是同一个String对象。做法最简单的就是使用同一个String对象,但这个有时很难保证。特别是我们很多的时候代码是分布式环境下的。

    比如,我们将用户名存在了redis里,线程每次同步的时候去redis里取一下数据,这样就很有可能导致产生新的String对象。这个时候就得使用intern()方法。上面的代码修改为:

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

    这样就是直接获取的是字符串的值本身,而不是取的String的对象,以此保证同一个字符串拿到的是同一个String对象,自然在同一个进程中就是同一个对象锁了。

    threadNum : 10
    Value:1
    Value:2
    Value:3
    Value:4
    Value:5
    Value:6
    Value:7
    Value:8
    Value:9
    Value:10
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    来源:http://t.zoukankan.com/5207-p-9592516.html
    String.intern():https://blog.csdn.net/tyyking/article/details/82496901

  • 相关阅读:
    关于大模型对未来影响的一点看法
    怎么配置electron-updater
    HTML躬行记(3)——WebRTC视频通话
    C/C++编译器工作原理
    C语言指针操作(五)*指向函数的指针
    文件I/O与标准I/O
    Webpack干货系列 | 在 Webpack 5 集成 ESLint 的方法
    FactoryBean原理
    项目能跑起来,一打开浏览器运行就报错
    bclinux aarch64 ceph 14.2.10 云主机 4节点 fio
  • 原文地址:https://blog.csdn.net/yzx3105/article/details/127654440