string(21) "{"docs":[],"count":0}" array(2) { ["docs"]=> array(0) { } ["count"]=> int(0) } java学习|图解多线程(二)不一样的加锁方式 - 爱码网

上文说到一些孪生的类,而且通过对比可以看到,线程安全的类的解决方案是对类中每个方法都加上synchronized关键字。而且不管是读取还是写入,都加了锁。 在上文结尾我们简单提到了一些别的加锁方法。今天我们就来看看不在方法上加锁的其他加锁方式(下面源代码如无特殊说明都是基于jdk11

1concurrentMap

每说到并发的集合类时,都会说到concurrentHashMap ,然后网上一搜可以看到好多相关的概念,segment,链表,红黑树,等等。现在我们一起来看看concurrentMap的加锁方式,同样选取的 put 方法,因为一般加锁都是对修改做的。

 jdk1.7中的put:

java学习|图解多线程(二)不一样的加锁方式

中间我们可以看到先是定位segment的位置,然后调用segmentput方法:

static final class Segment<K,V> extends ReentrantLock implements Serializable

java学习|图解多线程(二)不一样的加锁方式

Segment 是继承ReentrantLock , 在代码432行,有 tryLock() 这就是对这一块segment 加锁。 比直接对方法加锁细化了一步。

jdk 11中的put

java学习|图解多线程(二)不一样的加锁方式

在代码中我们可以看到在调用put时,重载了putVal , 然后注意划线的重点位置:先通过hash值取到对应的Node节点。 

然后对 进行加锁,这样把锁的粒度就降到了节点上。

本次对concurrentMap只探讨加锁方式,其他类如节点转换等, 我们以后再做讨论。

2.CopyOnWriteList

我们用的最多应该是ArrayList , LinkedList 。但我们在使用他们的时候,很多时候都是在方法里面新建,在方法里面进行使用,所以基本上不会出现线程不安全的问题。但如果同一个List被多个线程所使用,则有可能抛出并发修改的异常。 可能抛异常的代码如下:

public static void main(String[] args) {

    List<String> list = new ArrayList<>();

    Runnable read =()->{

        while (true){

            for (String s : list) {

                System.out.println(s);

                try {

                    Thread.sleep(1000);

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

            }

        }

    };

    Runnable write = ()->{

        while (true){

            list.add("aaa");

            try {

                Thread.sleep(1000);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

        }

    };

    Thread rThread = new Thread(read);

    Thread wThread = new Thread(write);

    wThread.start();

    rThread.start();

}

java学习|图解多线程(二)不一样的加锁方式

通过跟踪源代码可以看到,是循环调用next()时,我们同时想向里面add数据,就抛出了并发修改的异常。

java学习|图解多线程(二)不一样的加锁方式

我们把代码中的 List<String> list = new ArrayList<>();换成 CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(); 就不会上述异常了,小伙伴们可以自己试下。

CopyOnWriteArrayList 中,我们每一次的新增操作,都会复制一遍原有的数据,对复制的数据进行操作,操作完之后再设置回去

java学习|图解多线程(二)不一样的加锁方式

430,431行是获取原有数组, 432行是新建一个数组并扩容加1 433行是赋值。

 

434行是替换掉老的数组。这样读还是读的老数据,修改是对复制的副本进行的操作。

同时在代码中我们可以看到,加锁方式,由在方法体上加锁,变成了对Object 加锁,细化了锁的粒度。

总结:加锁是门艺术,是门学问。在concurrentHashMap部分,我特意把jdk1.7的源码找了出来,因为现在网上很多文章和视频一说起concurrentHashMap 就是segment 分段加锁,但实际上到了jdk11已经有很大变化了。

java学习|图解多线程(二)不一样的加锁方式

java学习|图解多线程(二)不一样的加锁方式

END

前期回顾:

Java学习|图说String(一):String的存储方式

环境变量|你还不知道怎么设置环境变量吗!?超详细步骤分解!

开始Java编程之路

QQ群:661749608

微信群请点击公众号菜单进微信群

们哦~

java学习|图解多线程(二)不一样的加锁方式

文字: 微笑的小小刀

排版:花音

相关文章: