多线程
- 什么是线程?
进程概念: 进程是指运行中的应用程序,每个进程都有自己独立的地址空间(内存空间),比如用户点击桌面的IE浏览器,就启动了一个进程,操作系统就会为该进程分配独立的地址空间。当用户再次点击左面的IE浏览器,又启动了一个进程,操作系统将为新的进程分配新的独立的地址空间。目前操作系统都支持多进程。线程概念:是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。线程有就绪、阻塞和运行三种基本状态。
1、线程是轻量级的进程
2、线程没有独立的地址空间(内存空间)
3、线程是由进程创建的(寄生在进程)
4、一个进程可以拥有多个线程-->这就是我们常说的多线程编程
5、线程有几种状态:
a、新建状态(new)
b、就绪状态(Runnable)
c、运行状态(Running)
d、阻塞状态(Blocked)
e、死亡状态(Dead)
线程--如何使用
在java中一个类要当作线程来使用有两种方法。
1、继承Thread类,并重写run函数
2、实现Runnable接口,并重写run函数
因为java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时在用继承Thread类方法来创建线程显然不可能java设计者们提供了另外一个方式创建线程,就是通过实现Runnable接口来创建线程。
- 什么是线程安全和线程不安全?
线程安全就是在多线程环境下也不会出现数据不一致,而非线程安全就有可能出现数据不一致的情况。
线程安全由于要确保数据的一致性,所以对资源的读写进行了控制,换句话说增加了系统开销。所以在单线程环境中效率比非线程安全的效率要低些,但是如果线程间数据相关,需要保证读写顺序,用线程安全模式 - 什么是自旋锁?sniplock
互斥锁和他类似,但是互斥锁会让没获取到资源的其他线程睡眠。线程想要获取共享资源就要先获取锁定资源的锁。当线程无法获得锁的时候就会挂起或者阻塞,这个操作是在内核态进行的。我们想充分发挥cpu资源,所以jvm采取一个自旋锁机制,让获取不到资源的线程进行空循环,来一直等待锁释放。缺点:如果资源被某个线程长时间占用,就会使其他线程一直处于空循环状态,白白浪费cpu资源。使用场景:线程占用资源的时间不长的。优点:由于线程状态不会改变,获取资源速度会很快。
- 什么是Java内存模型?
https://blog.csdn.net/javazejian/article/details/72772461
- 什么是CAS?
java.util.concurrent包建立在CAS之上,没有CAS就没有并发包CAS:compare and swap 比较交换 java.util.concurrent包中借助CAS实现了区别于synchronouse同步锁的一种乐观锁。CAS有三个操作数:V内存值,A预期的旧值,B要更新的新值;仅当内存值V和预期值A相等时,才会将内存值V更新为新值B,否则什么也不做。CAS原理:CAS是通过JNI实现的,java native interface,可以调用本地的C语言来控制计算机底层指令,
public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
这个就是被调用的本地方法,他会给cmpxchg底层指令加上lock锁,带有lock前缀的指令在运行期间会锁住主线,使得其他处理器暂时无法通过主线访问内存,但是代价昂贵;后来又做了优化,如果要访问的内存区域正好在处理器的缓存中被锁定的话就直接执行,其他的处理器无法读写该内存,能保证原子性,这个过程叫缓存锁定,
虽然CAS很好的解决了线程操作的原子性问题,但是CAS仍然存在三大问题:ABA问题,循环时间长开销大和只能保证一个共享变量的原子操作。AtomicInteger integer = new AtomicInteger(2); integer.compareAndSet(3, 4); System.out.println(integer.get());
CAS缺点:
1. ABA问题:
比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但可能存在潜藏的问题。如下所示:
现有一个用单向链表实现的堆栈,栈顶为A,这时线程T1已经知道A.next为B,然后希望用CAS将栈顶替换为B:
head.compareAndSet(A,B);
在T1执行上面这条指令之前,线程T2介入,将A、B出栈,再pushD、C、A,此时堆栈结构如下图,而对象B此时处于游离状态:
此时轮到线程T1执行CAS操作,检测发现栈顶仍为A,所以CAS成功,栈顶变为B,但实际上B.next为null,所以此时的情况变为:
其中堆栈中只有B一个元素,C和D组成的链表不再存在于堆栈中,平白无故就把C、D丢掉了。
从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
1 public boolean compareAndSet( 2 V expectedReference,//预期引用 3 4 V newReference,//更新后的引用 5 6 int expectedStamp, //预期标志 7 8 int newStamp //更新后的标志 9 )
实际应用代码:
1 private static AtomicStampedReference<Integer> atomicStampedRef = new AtomicStampedReference<Integer>(100, 0); 2 3 ........ 4 5 atomicStampedRef.compareAndSet(100, 101, stamp, stamp + 1);
2. 循环时间长开销大:
自旋CAS(不成功,就一直循环执行,直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。
3. 只能保证一个共享变量的原子操作:
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。
CAS与Synchronized的使用情景:
1、对于资源竞争较少(线程冲突较轻)的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。
2、对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。
补充: synchronized在jdk1.6之后,已经改进优化。synchronized的底层实现主要依靠Lock-Free的队列,基本思路是自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。
concurrent包的实现:
由于java的CAS同时具有 volatile 读和volatile写的内存语义,因此Java线程之间的通信现在有了下面四种方式:
1. A线程写volatile变量,随后B线程读这个volatile变量。
2. A线程写volatile变量,随后B线程用CAS更新这个volatile变量。
3. A线程用CAS更新一个volatile变量,随后B线程用CAS更新这个volatile变量。
4. A线程用CAS更新一个volatile变量,随后B线程读这个volatile变量。
Java的CAS会使用现代处理器上提供的高效机器级别原子指令,这些原子指令以原子方式对内存执行读-改-写操作,这是在多处理器中实现同步的关键(从本质上来说,能够支持原子性读-改-写指令的计算机器,是顺序计算图灵机的异步等价机器,因此任何现代的多处理器都会去支持某种能对内存执行原子性读-改-写操作的原子指令)。同时,volatile变量的读/写和CAS可以实现线程之间的通信。把这些特性整合在一起,就形成了整个concurrent包得以实现的基石。如果我们仔细分析concurrent包的源代码实现,会发现一个通用化的实现模式:
1. 首先,声明共享变量为volatile;
2. 然后,使用CAS的原子条件更新来实现线程之间的同步;
3. 同时,配合以volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信。
AQS,非阻塞数据结构和原子变量类(java.util.concurrent.atomic包中的类),这些concurrent包中的基础类都是使用这种模式来实现的,而concurrent包中的高层类又是依赖于这些基础类来实现的。从整体来看,concurrent包的实现示意图如下:
- 什么是乐观锁和悲观锁?
悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。 传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。再比如Java里面的同步原语synchronized关键字的实现也是悲观锁。乐观锁: 乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
- 什么是AQS?
- 什么是原子操作?在Java Concurrency API中有哪些原子类(atomic classes)?
- 什么是Executors框架?
- 什么是阻塞队列?如何使用阻塞队列来实现生产者-消费者模型?
- 什么是Callable和Future?
- 什么是FutureTask?
- 什么是同步容器和并发容器的实现?
- 什么是多线程?优缺点?
- 什么是多线程的上下文切换?
- ThreadLocal的设计理念与作用?
- ThreadPool(线程池)用法与优势?
- Concurrent包里的其他东西:ArrayBlockingQueue、CountDownLatch等等。
- synchronized和ReentrantLock的区别?
- Semaphore有什么作用?
- Java Concurrency API中的Lock接口(Lock interface)是什么?对比同步它有什么优势?
- Hashtable的size()方法中明明只有一条语句”return count”,为什么还要做同步?
- ConcurrentHashMap的并发度是什么?
- ReentrantReadWriteLock读写锁的使用?
- CyclicBarrier和CountDownLatch的用法及区别?
- LockSupport工具?
- Condition接口及其实现原理?
- Fork/Join框架的理解?
- wait()和sleep()的区别?
- 线程的五个状态(五种状态,创建、就绪、运行、阻塞和死亡)?
- start()方法和run()方法的区别?
- Runnable接口和Callable接口的区别?
- volatile关键字的作用?
- Java中如何获取到线程dump文件?
- 线程和进程有什么区别?
- 线程实现的方式有几种(四种)?
- 高并发、任务执行时间短的业务怎样使用线程池?并发不高、任务执行时间长的业务怎样使用线程池?并发高、业务执行时间长的业务怎样使用线程池?
- 如果你提交任务时,线程池队列已满,这时会发生什么?
- 锁的等级:方法锁、对象锁、类锁?
- 如果同步块内的线程抛出异常会发生什么?
- 并发编程(concurrency)并行编程(parallellism)有什么区别?
- 如何保证多线程下 i++ 结果正确?
- 一个线程如果出现了运行时异常会怎么样?
- 如何在两个线程之间共享数据?
- 生产者消费者模型的作用是什么?
- 怎么唤醒一个阻塞的线程?
- Java中用到的线程调度算法是什么
- 单例模式的线程安全性?
- 线程类的构造方法、静态块是被哪个线程调用的?
- 同步方法和同步块,哪个是更好的选择?
- 如何检测死锁?怎么预防死锁?