并发 - 每天五分钟搞定Java面试

面试中经常会被问到一些并发相关的问题,面试官会问
你了解并发编程吗?
你知道什么是线程安全吗?
怎么实现线程安全?
synchronized?
volatile?
CAS?
你知道concurrent包下面的并发集合类吗?
等等

多线程是在同一个程序内部并行执行,因此会对相同的内存空间进行并发读写操作。如果一个线程在读一个内存时,另一个线程正向该内存进行写操作,那进行读操作的那个线程将获得什么结果呢?是写操作之前旧的值?还是写操作成功之后的新值?或是一半新一半旧的值?或者,如果是两个线程同时写同一个内存,在操作完成后将会是什么结果呢?是第一个线程写入的值?还是第二个线程写入的值?还是两个线程写入的一个混合值?因此如没有合适的预防措施,任何结果都是可能的。而且这种行为的发生甚至不能预测,所以结果也是不确定性的。

并发

并发问题的产生有两个先决条件:多线程、共享变量(共享资源)

线程安全

什么是线程安全?
当多个线程访问某个类时,这个类始终都能表现出正确的行为,那就称这个类是线程安全的。

多线程

优点

资源利用率更好
程序设计在某些情况下更简单
程序响应更快

缺点

设计更复杂
线程上下文切换的开销
增加资源消耗,线程在运行的时候需要从计算机里面得到一些资源。除了CPU,线程还需要一些内存来维持它本地的堆栈。它也需要占用操作系统中一些资源来管理线程。
多线程编程中有三个重要的核心概念:原子性、可见性和顺序性

原子性

原子性和数据库中的事务的原子性一样,即一个操作(有可能包含有多个子操作)要么全部执行(生效),要么全部都不执行(都不生效)。

关于原子性,一个非常经典的例子就是银行转账问题:比如A和B同时向C转账10万元。如果转账操作不具有原子性,A在向C转账时,读取了C的余额为20万,然后加上转账的10万,计算出此时应该有30万,但还未来及将30万写回C的账户,此时B的转账请求过来了,B发现C的余额为20万,然后将其加10万并写回。然后A的转账操作继续——将30万写回C的余额。这种情况下C的最终余额为30万,而非预期的40万。

可见性

当多个线程并发访问共享变量时,一个线程对共享变量的修改,其它线程能够立即看到

顺序性

程序执行的顺序按照代码的先后顺序执行。
计算机为了优化程序执行效率,会对指令进行重排序。我们看到的代码顺序不一定是代码实际执行顺序。

共享变量

多线程可能操作到的某一个类,比如User,或者是某个类的属性User.name

实现线程安全(如何解决多线程并发问题)

保证原子性
CAS synchronized Lock
保证可见性
volatile synchronized Lock
保证顺序性
volatile 可以避免对指令进行重排序

ThreadLocal

ThreadLocal实质是转换了一种思路,把并发中的有个前提条件共享资源变成线程私有,不存在共享资源后,也就没有对共享资源进行访问所引发的数据不一致问题。但是ThreadLocal使用的场景很局限。因为除非是我们明确不需要在多线程中进行共享,才会设置为ThreadLocal。比如日期类SimpleDateFormat,以及数据库连接等。

关于synchronized,volatile,CAS可以参考下面的文章
http://ifeve.com/java-concurrency-thread-directory/
https://www.ibm.com/developerworks/cn/java/j-jtp06197.html
http://www.jasongj.com/java/thread_safe/
http://wiki.jikexueyuan.com/project/java-concurrency/synchronized-and-volatile.html

猜你喜欢

转载自blog.csdn.net/SMonkeyKing/article/details/82467814