JUC--原理篇1理论基础
问题:为什么需要多线程?
–解答:使用多线程的主要目的是提高CPU的执行效率,比如某个正在运行的线程处于等待状态时,就可以让CPU去执行另一个任务,充分利用CPU资源,提高系统性能。
多线程有两种形式:并行和并发,两者的本质区别是并行能够真正做到两个事件同时运行,而并发在宏观上是同时运行,微观上是交替运行。我们主要讨论的是并发。
问题:为什么会出现线程并发安全问题?
–解答:线程并发问题发生在多个线程间共享某个数据,又同时操作该数据的情况。本质上是由以下三个问题造成:可见性、原子性和有序性。
可见性问题指的是某个线程修改了某个变量后还没有写入主存(还在高速缓存中),另一个线程就从主存中读取了该变量的旧数据,并对旧数据进行操作。比如i一开始等于10,线程1将其修改为15,此时15存储在缓存中,此时线程2取出i(10),并将其修改为12,最后i的值可能是12也可能是15.
原子性问题:java中一些操作在底层可能是由多条指令构成,CPU在同一时刻只能执行一条指令,那么就可能有这种情况发生:某个线程的操作执行一半时,CPU被移交给另一个线程,操作的数据又是同一个,最终也得不到我们想要的结果。
有序性问题:程序执行时,可能不会按照我们编写的代码顺序执行。因为为了提高性能,编译器和处理器常常会对指令做重排序。
编译器优化的重排序。编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
指令级并行的重排序。现代处理器采用了指令级并行技术(Instruction-Level Parallelism, ILP)来将多条指令重叠执行。如果不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。
内存系统的重排序。由于处理器使用缓存和读 / 写缓冲区,这使得加载和存储操作看上去可能是在乱序执行
问题:java是如何解决并发问题的?
–解答:(在java中通过JMM(java 内存模型)来解决并发问题。)使用了volatile、synchronized和final关键字以及Happens-Before规则。
使用synchronized和Lock可以保证操作执行的原子性,只有一个线程执行对应代码块。
使用volatile关键字可以保证被修改的值立即更新到主存中,这样另一个线程就不会取出旧数据了。(另外使用synchronized和Lock也能够保证可见性,因为在锁释放之前,会将共享变量的值更新到主存中)
可以使用volatile保证一定的有序性,synchronized和Lock由于保证只有一个线程可以执行同步代码块,也能保证有序性。