多线程并发与String的内存模型介绍,并完美解决锁的并发问题

       首先需要了解String的内存模型,有常量池,堆,那么何时访问的是常量池,何时又是访问的堆,需要提前了解,当然下文也会介绍,此处就不在过多说明;另外线程的内存模型也要了解一下,尤其是线程栈。接下来我们就来做一个简单的实验,代码如下:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        final Test1 test1 = new Test1();
        CountDownLatch latch = new CountDownLatch(20);
        for(int i=0;i<20;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        String a = new String("1");
                        test1.add(a);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    latch.countDown();
                }
            }).start();
        }
        latch.await();
        System.out.println(test1.a);
       
    }
    
}

class Test1{
    public int a =0;
    void add(String i) throws InterruptedException {
        synchronized (i){
            int c=a+1;
            Thread.sleep(200);
            a=c;
        }
    }
}

从上面看,开启20个线程,分别对a进行加1操作,觉得这段代码的执行结果会是多少?是20吗?显然不是。此处new String会在内存的堆中单独创建对象,每个线程都是各自的对象,然后在synchronize中对这个对象加锁,其实是互不竞争的,所以最后的运行结果可能是1,也可能是其他。那么如何修改才能得到结果20呢?那就是让20个线程相互竞争,可以通过String的intern获取常量池中相同内容的对象,然后对该对象加锁,也就是说,只要内容相同,返回的对象就是同一个,然后用synchronize加锁就可以成功,只需调整一下同步块的代码即可:

synchronized (i.intern())

此时获取到结果就是20。

有的人可能会说,直接在方法上同步就可以了,当然这种方式也是可以的,不过性能肯定要差,主要体现在两方面来说:

1、对方法加同步,那整个方法执行完成后才被其他线程执行,锁的粒度太大了。

2、锁的范围太大了,此次测试只是将i设置成为相同了,所以20个线程都对这个i竞争,如果i不同,方法上的同步也会造成20个线程竞争;而采用i.intern(),可以把锁给分散开,范围更小,性能就会更好。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章