java中怎么给方法加锁_Java中,我会用ArrayList,怎么还要会用CopyOnWriteArrayList

前言

之前的文章已经说过了java开发中,保存集合数据是用ArrayList还是LinkedList,了解下:Java编程中我该用ArrayList还是LinkedList?

平常面试过程中问的最多的就是 HashMap,当问到可以保证线程安全的并发容器时,最熟悉的就是ConcurrentHashMap,也了解下:我已经学了HashMap,怎么还要会用ConcurrentHashMap

但是如果问 “有没有类似ArrayList,又保证线程安全的容器类”,首先我们都知道ArrayList是线程不安全的,所以很多人估计会说 Vector,没错,这个就好像HashTable,每个方法都用了Synchronized去声明,保证线程安全。也可以通过 Collections 方法来保证线程安全。

Collections.synchronizedList(new ArrayList()) 

我们都知道Vector是一个很老的技术了,性能很差,扩容也有问题。

但是面试官更多的是想听到 CopyOnWriteArrayList,这个类如ConcurrentHashMap样,都在java的JUC包中,属于并发容器类。

6b8afaf9ed88e4f10ab1438c1f087aaf.png

CopyOnWriteArrayList

啥叫CopyOnWrite?

CopyOnWrite简称COW,看意思就晓得是写时复制,这是一种计算机程序设计领域的优化策略。简单来说就是多个线程同时调用相同的资源,某个线程试图修改资源的内容时,系统才会真正复制一份专用副本(private copy)给该线程,而其他线程获取的资源仍然保持不变。也是一种延时懒惰策略。

这种系统机制在很多的场景中有用到,可以去了解下,linux下copy-on-write机制,mysql B-tree索引数据结构就是采用了copy-on-write,还有Kafka。。。。等等。

这里呢我们再从程序的角度再来解释下CopyOnWrite机制。

我们往集合中添加元素的时候,先copy一份原有数据数组,在copy数组中进行写操作,完成写操作后利用这个copy数组复制给成员变量的数组(array)。这么做的目的就是,多线程竞争来读的时候不需要加锁,因为多个线程写时不时直接操作成员变量的数组(array),这里呢,相信大家知道这是一种读写分离的思想。如图:

8a3f10059780be17c2d5a0393a43b8ec.png

看下CopyOnWriteArrayList的源码就更明白了:

//成员变量数组private transient volatile Object[] array;//添加元素代码public boolean add(E e) {  //获得锁        final ReentrantLock lock = this.lock;        lock.lock();        try {            Object[] elements = getArray();            int len = elements.length;          //复制一个新的数组            Object[] newElements = Arrays.copyOf(elements, len + 1);            newElements[len] = e;          //将新的数组指向原来的引用 (array)            setArray(newElements);            return true;        } finally {            lock.unlock();        }    }//获取元素代码public E get(int index) {  return get(getArray(), index);}

是不是一眼明了。CopyOnWriteArrayList的增删改都需要获得锁,而读操作不需要获得锁,提高并发。如果读写锁互斥的话,会导致写锁阻塞大量读操作,影响并发性能。

看完这个代码,有的人会问,ReentrantLock也是一种悲观锁啊,那岂不是和synchronized声明的方法一样了?!!!他们两个都是独占锁!!下面说一下为啥用 ReentrantLock。

相比synchronized,ReentrantLock是一套互斥锁,底层使用了CAS,有着其他的高级特性(我前面的文章也提到过java中各种锁):

1、synchronized是一种非公平锁,而ReentrantLock可以声明为公平锁,也就是先等待的线程先获得锁,不会造成线程饥饿现象。

2、等待可中断,持有锁的线程长期不释放的时候,等待的线程可放弃,从一定程度上防止死锁。

3、ReenTrantLock控制线程非常的灵活,通过Condition来实现分组唤醒需要唤醒的线程们。

从锁粒度分析的话,ReenTrantLock优于Synchronized。

问题又来了,成员变量数组array为啥用了volatile和transient去修饰。

如果一个线程修改完数据并赋值给成员变量数组array了,读线程要怎么晓得这个变化。这个volatile就保证了 成员变量数组array在多线程中的可见性。但是不能绝对的保证一致性。

至于transient的作用,我就不清楚了,只晓得对象序列化的时候,标记为transient的变量不会被序列化。

总的来说,CopyOnWriteArrayList适合读多写少,不要求数据强一致性的场景。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值