关于copyonwriteArraySet的一点疑问

辉酱OvO 2020-06-29 11:35:00
最近在学习juc,看到实现set安全时有这样的类,copyonwriteArraySet,底层是使用了copyonwriteArraylist,但是研究源码发现稍有区别,也有些疑惑。(查看源码的版本是:OPENJDK8)

首先描述一下该类的方法:当我们往set中add时

调用该类时使用的是这个方法,点进去


这里我理解是首先获得当前的快照,然后判断新元素是否在快照的数组中。如果存在则返回false,不存在则返回加入


这个是加入方法,与copyonwriteArrayList的add方法类似,中间多了个if判断,我的理解是:因为之前判断的时候是没有加锁的,所以可能出现数据不一致的情况,因此重新获取一遍数据。


我的疑问在第二个图的indexOf方法中。因为到这里为止,线程都是没有锁的。假如我在判断indexOf是否存在的过程中,其他线程修改了这个数组,那么会不会出现越界情况呢?(因为添入如删除都是使用的复制方法,那么数组长度肯定会改变,那么我for循环遍历的时候岂不是有问题呢?)
附上IndexOf的方法内部



...全文
4925 10 打赏 收藏 转发到动态 举报
写回复
用AI写文章
10 条回复
切换为时间正序
请发表友善的回复…
发表回复
辉酱OvO 2020-07-01
  • 打赏
  • 举报
回复
引用 9 楼 扭扭捏捏的我 的回复:
clear操作只是将array指向了新数组 而不是改变了原数组


哦哦哦...我明白过来了,这个快照指向的那个数组是不变的。
梦在明月 2020-07-01
  • 打赏
  • 举报
回复
clear操作只是将array指向了新数组 而不是改变了原数组
梦在明月 2020-07-01
  • 打赏
  • 举报
回复
先get到了数组array 可能在刚拿到后就被指向新数组 也可能刚clear就被你拿到了 但是不会影响addIfAbsent(e, snapshot)的操作 array千万变 只取了其中的一个 然后进入addIfAbsent(e, snapshot)后就上锁了 上完锁再次进行校验确保了线程安全 这个锁和clear是同一个锁
梦在明月 2020-07-01
  • 打赏
  • 举报
回复
引用 6 楼 辉酱OvO 的回复:
[quote=引用 5 楼 扭扭捏捏的我 的回复:] 我好想明白你的疑惑在哪里了 你是担心原来的那个数组 也就是current==snapshot这种情况下 长度被改了是吧?完全不可能的啊 数据的长度是固定了的 不能改 元素只能置null 不会出现下标越界啊喂
恩,我也疑惑确实是那里。但是你想,当我有个get线程在遍历集合的时候,有另外的线程调用了clear方法

 public void clear() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            setArray(new Object[0]);
        } finally {
            lock.unlock();
        }
    }
这是它的clear方法,可以看到是new了个新的长度为0的数组替换了原来的数组。那这个时候不会出问题么[/quote] 不会 因为这个新生成的数组不是 indexOf方法内的数组 是两个引用 调用indexOf之前 原数组的引用已经被get到了
public boolean addIfAbsent(E e) {
        Object[] snapshot = getArray();
        return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
            addIfAbsent(e, snapshot);
    }
梦在明月 2020-06-30
  • 打赏
  • 举报
回复
我好想明白你的疑惑在哪里了 你是担心原来的那个数组 也就是current==snapshot这种情况下 长度被改了是吧?完全不可能的啊 数据的长度是固定了的 不能改 元素只能置null 不会出现下标越界啊喂
梦在明月 2020-06-30
  • 打赏
  • 举报
回复
引用 3 楼 辉酱OvO 的回复:
[quote=引用 1 楼 扭扭捏捏的我 的回复:]
private boolean addIfAbsent(E e, Object[] snapshot) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] current = getArray();
            int len = current.length;
            if (snapshot != current) {
                // Optimize for lost race to another addXXX operation
                int common = Math.min(snapshot.length, len);
                for (int i = 0; i < common; i++)
                    if (current[i] != snapshot[i] && eq(e, current[i]))
                        return false;
                if (indexOf(e, current, common, len) >= 0)
                        return false;
            }
            Object[] newElements = Arrays.copyOf(current, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }
其实你再往下点进去addIfAbsent就能发现 他是上了锁的 而且上锁后再次进行了判断
你看我第二张图,它是先确定没有重复的(执行indexOf那个方法),判断出不存在重复的,然后才去跑了底下那个带锁的方法。我的疑惑是他执行这个indexOf方法的时候,并不能保证别的线程没有修改过这个数组呀,如果别的线程改小了数组的长度,那这个IndexOf方法岂不是会出现越界问题么?[/quote] Object[] current = getArray(); int common = Math.min(snapshot.length, len);
辉酱OvO 2020-06-30
  • 打赏
  • 举报
回复
引用 5 楼 扭扭捏捏的我 的回复:
我好想明白你的疑惑在哪里了 你是担心原来的那个数组 也就是current==snapshot这种情况下 长度被改了是吧?完全不可能的啊 数据的长度是固定了的 不能改 元素只能置null 不会出现下标越界啊喂


恩,我也疑惑确实是那里。但是你想,当我有个get线程在遍历集合的时候,有另外的线程调用了clear方法

public void clear() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
setArray(new Object[0]);
} finally {
lock.unlock();
}
}


这是它的clear方法,可以看到是new了个新的长度为0的数组替换了原来的数组。那这个时候不会出问题么
梦在明月 2020-06-29
  • 打赏
  • 举报
回复
private boolean addIfAbsent(E e, Object[] snapshot) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] current = getArray();
            int len = current.length;
            if (snapshot != current) {
                // Optimize for lost race to another addXXX operation
                int common = Math.min(snapshot.length, len);
                for (int i = 0; i < common; i++)
                    if (current[i] != snapshot[i] && eq(e, current[i]))
                        return false;
                if (indexOf(e, current, common, len) >= 0)
                        return false;
            }
            Object[] newElements = Arrays.copyOf(current, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }
其实你再往下点进去addIfAbsent就能发现 他是上了锁的 而且上锁后再次进行了判断
辉酱OvO 2020-06-29
  • 打赏
  • 举报
回复
引用 1 楼 扭扭捏捏的我 的回复:
private boolean addIfAbsent(E e, Object[] snapshot) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] current = getArray();
int len = current.length;
if (snapshot != current) {
// Optimize for lost race to another addXXX operation
int common = Math.min(snapshot.length, len);
for (int i = 0; i < common; i++)
if (current[i] != snapshot[i] && eq(e, current[i]))
return false;
if (indexOf(e, current, common, len) >= 0)
return false;
}
Object[] newElements = Arrays.copyOf(current, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}



其实你再往下点进去addIfAbsent就能发现 他是上了锁的 而且上锁后再次进行了判断






你看我第二张图,它是先确定没有重复的(执行indexOf那个方法),判断出不存在重复的,然后才去跑了底下那个带锁的方法。我的疑惑是他执行这个indexOf方法的时候,并不能保证别的线程没有修改过这个数组呀,如果别的线程改小了数组的长度,那这个IndexOf方法岂不是会出现越界问题么?
辉酱OvO 2020-06-29
  • 打赏
  • 举报
回复
你看我第二张图,它是先确定没有重复的(执行indexOf那个方法),判断出不存在重复的,然后才去跑了底下那个带锁的方法。我的疑惑是他执行这个indexOf方法的时候,并不能保证别的线程没有修改过这个数组呀,如果别的线程改小了数组的长度,那这个IndexOf方法岂不是会出现越界问题么?

50,528

社区成员

发帖
与我相关
我的任务
社区描述
Java相关技术讨论
javaspring bootspring cloud 技术论坛(原bbs)
社区管理员
  • Java相关社区
  • 小虚竹
  • 谙忆
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧