同步类容器和并发类容器
同步类容器
如古老的Vector、Hashtable。这些容器的同步功能其实都是有JDK的Collection.synchronized等工厂方法去创建实现的。其底层的机制无非就是用传统的synchronized关键字对每个公共的方法都进行同步,使得每次操作只能有一个线程去访问容器的状态。
并发类容器
jdk5.0以后提供了多种并发类容器来替代同步类容器从而改善性能,并发类容器时专门针对并发设计的:使用ConcurrentHashMap来代替基于散列的Hashtable,并添加了一些常见符合操作的支持;使用CopyOnWriteArrayList代替Vector,并发的CopyOnWriteArraySet,以及并发的Queue:ConcurrentLinkedQueue和LinkedBlockingQueue,前者是高性能的队列,后者是以阻塞形式的队列。具体形式的Queue还有很多如:ArrayBlockingQueue、PriorityBlockingQueue、SynchronousQueue等。
Copy-On-Write容器
JDK中的COW容器有两种,CopyOnWriteArrayList和CopyOnWriteArraySet,COW容器非常的有用,可以在非常多的并发场景中使用到。
CopyOnWrite容器即写时复制的容器,通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后往新的容器里添加元素。元素添加完成之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何的元素,所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器,非常适合读多写少的情况
何为线程安全的类?
在线程安全性的定义中,最核心的概念就是 正确性。当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么这个类就是线程安全的。
Java中常见的线程安全的类
1,通过synchronized 关键字给方法加上内置锁来实现线程安全:
Timer,TimerTask,Vector,Stack,HashTable,StringBuffer
2,原子类Atomicxxx—包装类的线程安全类:
如AtomicLong,AtomicInteger等等,通过Unsafe 类的native方法实现线程安全的。
3,BlockingQueue 和BlockingDeque:
通过使用定义为final的ReentrantLock作为类属性显式加锁实现同步的
4,CopyOnWriteArrayList和 CopyOnWriteArraySet
ReentrantLock实现同步
5,Concurrentxxx:分段锁
6,ThreadPoolExecutor:使用了ReentrantLock显式加锁同步
多线程编程的三个核心概念
- 原子性:一个操作(有可能包含有多个子操作)要么全部执行(生效),要么全部都不执行(都不生效)。
- 可见性(缓存一致性问题):当多个线程并发访问共享变量时,一个线程对共享变量的修改,其它线程能够立即看到。
- 顺序性:程序执行的顺序按照代码的先后顺序执行。
如何解决多线程并发
- 保证原子性:锁和同步方法/同步代码块(通过排他性进而保证了目标代码的原子性);CAS(开销比锁小)。
- 保证可见性:volatile(不能保证原子性,开销比锁小);锁和synchronized(利用happen-before原则);final关键字;
- 保证顺序性:volatile(禁止指令重排);synchronized和锁(排他性,保证某一时刻只允许一条线程操作)。
除了从应用层面保证目标代码段执行的顺序性外,JVM还通过被称为happens-before原则隐式地保证顺序性。两个操作的执行顺序只要可以通过happens-before推导出来,则JVM会保证其顺序性,反之JVM对其顺序性不作任何保证,可对其进行任意必要的重新排序以获取高效率。
happen-before原则
Java内存模型中定义的两项操作之间的偏序关系。
A操作先行发生于操作B,是说在发生操作B之前,操作A产生的影响都能被操作B观察到。
具体的定义为:
1)如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
2)两个操作之间存在happens-before关系,并不意味着Java平台的具体实现必须要按照happens-before关系指定的顺序来执行。如果重排序之后的执行结果,与按happens-before关系来执行的结果一致,那么这种重排序并不非法(也就是说,JMM允许这种重排序)。
八大原则:
- 单线程:在同一个线程中,书写在前面的操作happen-before后面的操作。
- 锁的:同一个锁的unlock操作happen-before此锁的lock操作。
- volatile的:对一个volatile变量的写操作happen-before对此变量的任意操作(当然也包括写操作了)。
- 传递性原则:如果A操作 happen-before B操作,B操作happen-before C操作,那么A操作happen-before C操作。
- 线程启动:同一个线程的start方法happen-before此线程的其它方法。
- 线程中断:对线程interrupt方法的调用happen-before被中断线程的检测到中断发送的代码。
- 线程终止:主线程A等待子线程B完成,当子线程B执行完毕后,主线程A可以看到线程B的所有操作。也就是说,子线程B中的任意操作,happens-before join()的返回。
- 对象创建:一个对象的初始化完成先于他的finalize方法调用。
Java内存模型
Java内存模型(JMM)规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存(本地线程副本),线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。
JMM是一种规范,目的是解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。
内存模型怎么解决缓存一致性问题
1、通过在总线加LOCK#锁的方式。如果对总线加LOCK#锁的话,也就是说阻塞了其他CPU对其他部件访问(如内存),从而使得只能有一个CPU能使用这个变量的内存。
2、通过缓存一致性协议(Cache Coherence Protocol)。当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。
如何确保线程安全?
在Java中可以有很多方法来保证线程安全,诸如:
1)通过加锁(Lock/Synchronized)保证对临界资源的同步互斥访问;
2) volatile关键字,轻量级同步机制,但不保证互斥性原子性;(不能确保实现线程安全!!)
3)使用不变类 和 线程安全类(原子类,并发容器,同步容器等)。
4)利用CAS操作(非阻塞同步)
什么是Java Timer类?如何创建一个有特定时间间隔的任务?
Timer是一个调度器,可以用于安排一个任务在未来的某个特定时间执行或周期性执行。TimerTask是一个实现了Runnable接口的抽象类,我们需要去继承这个类来创建我们自己的定时任务并使用Timer去安排它的执行。
阻塞队列、非阻塞队列和普通队列
实现一个线程安全的队列有两种方式:一种是使用阻塞算法,另一种是使用非阻塞算法。
阻塞队列是与普通队列的区别有两点
1.阻塞队列获取元素时,如果队列为空,则会等待队列有元素,否则就阻塞队列(普通队列返回结果,无元素)
2.阻塞队列放入元素时,如果队列满,则等待队列,直到有空位置,然后插入。(普通队列,要么直接扩容,要么直接无法插入,不阻塞)
阻塞队列和非阻塞队列区别:
1,使用阻塞算法的队列可以用一个锁(入队和出队用同一把锁)或两个锁(入队和出队用不同的锁)等方式来实现。
2,非阻塞的实现方式是使用循环CAS的方式来实现。
非阻塞队列ConcurrentLinkedQueue
ConcurrentLinkedQueue是一个基于链接节点的无界线程安全队列,它采用先进先出的规则对节点进行排序,当我们添加一个元素的时候,它会添加到队列的尾部;当我们获取一个元素时,它会返回队列头部的元素。
入队和出队操作均利用CAS(compare and set)更新,这样允许多个线程并发执行,并且不会因为加锁而阻塞线程,使得并发性能更好。
阻塞队列类型
- ArrayBlockingQueue :一个由数组结构组成的有界阻塞队列。
- LinkedBlockingQueue :一个由链表结构组成的无界阻塞队列。
- PriorityBlockingQueue :一个支持优先级排序的无界阻塞队列。
- DelayQueue:一个使用优先级队列实现的无界阻塞队列。layQueue中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。
- SynchronousQueue:一个不存储元素的阻塞队列。无界、无缓冲的等待队列,可以认为SynchronousQueue是一个缓存值为1的阻塞队列。
- LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
什么是阻塞队列?如何使用阻塞队列来实现生产者-消费者模型?
java.util.concurrent.BlockingQueue的特性是:当队列是空的时,从队列中获取或删除元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操作会被阻塞。特别地,阻塞队列不接受空值,当你尝试向队列中添加空值的时候,它会抛出NullPointerException。另外,阻塞队列的实现都是线程安全的,所有的查询方法都是原子的并且使用了内部锁或者其他形式的并发控制。
同步容器(强一致性)
同步容器指的是 Vector、Stack、HashTable及Collections类中提供的静态工厂方法创建的类。1)Vector实现了List接口,Vector实际上就是一个数组,和ArrayList类似,但是Vector中的方法都是synchronized方法,即进行了同步措施
2)Stack也是一个同步容器,它的方法也用synchronized进行了同步,它实际上是继承于Vector类;
3)HashTable实现了Map接口,它和HashMap很相似,但是HashTable进行了同步处理,而HashMap没有。