接着昨天的例子,我们利用显示锁Lock实现了生产/消费者例子,今天介绍一些线程安全的类,首先用一个BlockingQueue来实现缓冲区的读/写整数。
BlockingQueue是一个接口,其有三个实现类(如果你愿意也可以实现一个自己的阻塞队列),ArrayBlockingQueue、LinkedListQueue、PriorityBlockingQueue。
我们先用LinkedListQueue实现缓冲区的例子,接着试着自己写一个阻塞队列(顺便阅读一下源码)。下面是代码:
put()、make()函数很重要!
下面自己实现了一个简单的阻塞队列:
实现:用数组实现,Integer类型(有心的读者可以自己实现泛型),synchronized形式实现同步(读者也可以自己实现显示加锁Lock,可以参考前一篇)。在数组尾部添加数据表示插入队列,获取并删除数组第一个数据表示获取队列数据(需要一次循环更新数值)。
使用:只需将上述代码的第10行做出修改即可,即用自己的队列替代LinkedBlockingQueue。
变量:size变量表示当前队列里有多少个数,capacity表示队列容量。每次操作之前都要对size、capacity做出判断!
之前同步篇介绍了2中同步的方法,今天在补充一种“奇招”,semaphore信号量(操作系统里头有这个概念)。它主要用于限制访问共享资源的显成熟,在访问资源之前,需要获取许可,在访问之后,释放许可!下面还是用那个存取款示例来演示:
PipedReader、PipedWriter(管道)
PipedReader:允许线程向管道写数据;
PipedWriter:允许不同线程从同一管道取数据;
PipedReader的建立必须在构造器中与一个PipedWriter相关联
同步集合
Java集合框架中的类不是线程安全的。Collections类提供了6个静态方法来讲集合转成同步版本:
迭代器具有快速失败的特性,如果当前的集合被另一个线程修改,而集合在使用迭代器,那么迭代器会通过抛出异常来结束。如果要在集合上遍历,则必须同步,示例如下:
CountDownLatch
用途:用来同步一个或多个任务,强制它们等待由其他任务执行的一组操作完成,来看看它是如何工作的:
1. 首先你得在你的程序中创建一个CountDownLatch对象,假定有两个工作组(领导组10人和员工组100人)要去吃饭,现在为了展示领导T恤下属,所有员工吃完后饭后才允许领导吃(好像不现实,就是例子而已)。假定每个工作组内部人员的吃饭这个动作相互不影响(因此不需要进行同步控制)。当某一个领导想要去吃饭的时候,发现有员工(计数大于0)还没吃好饭,那么他就必须等待。每当一个员工吃完饭,计数减1。下面是示例:
CyclicBarrier(书本翻译:循环栅栏)可以参考资料:
Java并发编程:CountDownLatch、CyclicBarrier和Semaphore
主要作用是:在某一个barrier(栅栏)等待一组任务,所有任务执行好了之后,在一起开始执行接下去的任务,循环进行!CountDownLatch则只能执行一次!
免锁容器:
免锁容器的策略是:对容器的修改可以与读取操作同时进行,只要读取者只能看到完成修改后的结果即可!
修改是在容器数据结构的某一个部分的副本中进行的,这个副本在修改过程中时不可视的。
只有修改完成后,被修改的结构才会主动与数据结构进行交换,之后读取者就可以看到这个修改了!
主要的类有:CopyOnWriteArrayList CopyOnWriteArraySet、ConcurrentHashMap、ConcurrentLinkedQueue
CopyOnWriteArraySet使用CopyOnWriteArrayList 来实现,在这两者上进行写操作,将导致java基础的线程同步有哪些创建整个底层数组的副本,源数组不变!
ConcurrentHashMap、ConcurrentLinkedQueue使用了类似的技术,允许并发的读取和写入,但是容器中只有部分内容而不是整个容器可以被复制和修改!
示例:CopyOnWriteArrayList读写操作(转载)
CopyOnWriteArrayList详解
其实我有一个问题:当一个线程在读取数据时,另一个线程修改完数据想要更新容器时,这时候是怎么处理的?首先想到的就是等待读取操作的结束,然后进行更新,这也是可以合理的!因为是可以进行并发操作的,所以可能会有其他线程也在遍历容器,这时候怎么处理?问题在复杂一点,如果有多个写入操作在等待,这又如何处理(FIFO队列解决?)!
总结:在以读取操作为主的程序中,使用这些线程安全的类库性能会更好!如果有频繁的写入操作,那么synchronized来的适合!
乐观加锁:Atomic类允许执行所谓的“乐观加锁”,意思是:当执行某项计算时,实际上并没有使用互斥。但是当计算完成,准备更新这个Atomic对象时,你需要使用一个叫compareAndSet()方法将新值和旧值进行比较,不一致则表示操作失败——表明已经有对象对其进行了修改!
优点:没有了互斥开销(获得锁和释放锁),运算速度更快!
缺点:的处理好当某一个操作失败之后应高执行的动作!
ReadWriteLock
ReadWriteLock允许同时又多个读取线程,只要它们不试图写入即可。如果写锁已经被其他线程持有,那么读取线程都不能访问,直到写锁被释放!
Java的并发集合允许读写同时进行,如果不是特殊需要,尽量用并发集合实现!
最后介绍一种多线程模型的替换方式:活动对象(Active Object,《Think in Java》中的术语)
主要的工作模式是:每个对象都维护自己的一个工作器线程和消息队列,并且所有 对这种对象的请求都将进入队列(可以说是无界的),任何时刻都只能运行一个。当向一个活动对象发送消息时,这条消息会转变成为一个任务,该任务会**入到这个对象的队列中,等待执行。这个时候Future有出场了,它可以返回某一任务执行的结果,下面是《Think in java》里的一个示例:
版权声明:
本文来源网络,所有图片文章版权属于原作者,如有侵权,联系删除。
本文网址:https://www.bianchenghao6.com/h6javajc/19037.html