并发(1)

目录

1.多线程的出现是要解决什么问题的?本质什么?

2.Java是怎么解决并发问题的?

3.线程安全有哪些实现思路?

4.如何理解并发和并行的区别?

5.线程有哪几种状态?分别说明从一种状态到另一种状态转变有哪些方式?


1.多线程的出现是要解决什么问题的?本质什么?

CPU,内存,I/O设备的速度是有极大差异的,为了合理利用CPU的高性能,平衡这三者的速度差异,计算机体系结构,操作系统,编译程序都做出了共享,主要体现为:

CPU增加了缓存,以均衡与内存的速度差异;//导致可见性问题

操作系统增加了进程,线程,以分时复用CPU,进而均衡CPU与I/O设备的速度差异;//导致原子性问题

编译程序优化指令执行次序,使得缓存能够得到更加合理的利用。//导致有序性问题

2.Java是怎么解决并发问题的?

Java内存模型是个很复杂的规范,具体看Java内存模型详解。

理解的第一个维度:核心知识点

JMM本质上可以理解为,Java内存模型规范了JVM如何提供按需禁用缓存和编译优化的方法。

具体来说,这些方法包括:

volatile,sychronized和final三个关键字

Happens-Before规则

理解的第二个维度:可见性,有序性,原子性

原子性

在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。请分析以下哪些操作是原子性操作:

上面4个只有语句1的操作具备原子性。

也就是说,只有简单的读取,赋值(而且必须是将数字赋值给某个变量,变量之间的相互赋值不是原子操作)才是原子操作。

从上面可以看出,Java内存模型只保证了基本读取和赋值是原子性操作,如果要实现更大范围操作的原子性,可以通过synchronized和Lock来实现。由于synchronized和Lock能够保证任意时刻只有一个线程执行该代码块,那么自然就不存在原子性问题了,从而保证了原子性。

可见性

Java提供了volatile关键字来保证可见性。

当一个共享变量被volatile修饰时,他会保证修改的值会立刻被更新到主存,当有其他线程需要读取时,他会去内存中读取新值。

而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。

另外,通过synchronized和Lock也能够保证可见性,sychronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将变量的修改刷新到主存当中。因此可以保证可见性。

有序性

在Java里面,可以通过volatile关键字来保证一定的“有序性"。另外可以通过sychronized和Lock来保证有序性。很显然,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。当然JMM是通过Happens-Before规则来保证有序性的。

3.线程安全有哪些实现思路?

1.互斥同步

sychronized和ReentrantLock

2.非阻塞同步

互斥同步最主要的问题就是线程阻塞和唤醒所带来的性能问题,因此这种同步也称为阻塞同步。

互斥同步属于一种悲观的并发策略,总是认为只要不去做正确的同步措施,那就肯定会出现问题。无论共享数据是否真的出现竞争,他都要进行加锁(这里讨论的是概念模型,实际上虚拟机会优化掉很大一部分不必要的加锁),用户态转换,维护锁计数器和检查是否有被阻塞的线程唤醒等操作。

CAS

随着硬件指令集的发展,我们可以使用基于冲突检测的乐观并发策略:先进行操作,如果没有其他线程争用共享数据,那操作就成功了,否则采取补偿措施(不断地重试,直到成功为止)。这种乐观的并发策略的许多实现都不需要将线程阻塞,因此这种同步操作称为非阻塞同步。

乐观锁需要操作和冲突检测这两个步骤具备原子性,这里就不能再使用互斥同步来保证了,只能靠硬件来完成。硬件支持的原子性操作最典型的是:比较并交换(Compare-and-Swap,CAS)。CAS指令需要有3个操作数,分别是内存地址V,旧的预期值A和新值B。当执行操作时,只有当V的值等于A,才将V的值更新为B。

AtomicInteger

J.U.C包里面的整数原子类AtomicInteger,其中的compareAndSet()和getAndIncrement()等方法都使用了Unsafe类的CAS操作。

3.无同步方案

要保证线程安全,并不是一定就要进行同步。如果一个方法本来就不涉及共享数据,那他自然就无须任何同步措施去保证正确性。

栈封闭

多个线程访问同一个方法的局部变量时,不会出现线程安全问题,因为局部变量存储在虚拟机栈中,属于线程私有的。

线程本地存储(Thread   Local  Storage)

如果一段代码中所需要的数据必须与其他代码共享,那就看看这些共享数据的代码能否保证在同一个线程中执行。如果能保证,我们就可以把共享数据的可见范围限制在同一个线程之内,这样,无须同步也能保证线程之间不出现数据争用的问题。

4.如何理解并发和并行的区别?

并发是指一个处理器 同时处理多个任务。

并行是指多个处理器或者多核的处理器同时处理多个不同的任务。

5.线程有哪几种状态?分别说明从一种状态到另一种状态转变有哪些方式?

新建(New)

创建后尚未启动。

可运行(Runnable)

可能正在运行,也可能正在等待CPU时间片。

包含了操作系统线程状态中的Runnuing和Ready。

阻塞(Blocking)

等待获取一个排他锁,如果其线程释放了锁就会结束此状态。

无限期等待(Waiting)

等待其他线程显式地唤醒,否则不会分配CPU时间片。

并发(1)_第1张图片

限期等待(Timed  Waiting)

无需等待其他线程显式地唤醒,在一定时间之后会被系统自动唤醒。

调用Thread.sleep()方法使线程进入限期等待状态时,常常用”使一个线程睡眠“进行描述。

调用Object.wait()方法进入限期等待或者无限期等待时,常常用”挂起一个线程“进行描述。

睡眠和挂起是用来描述行为,而阻塞和等待用来描述状态。

阻塞和等待的区别在于,阻塞是被动的,他是等待获取一个排他锁。而等待是主动的,通过调用Thread.sleep()和Object.wait()等方法进入。

并发(1)_第2张图片

死亡(Terminated)

可以是结束任务之后自己结束,或者产生了异常而结束。

你可能感兴趣的:(面试题,并发,并发)