Java多线程编程技术方案原理
一 ,多线程相关的一些概念
1,线程和进程:
线程指的是进程中一个单一顺序的控制流,
进程中可以并发多个线程,每条线程并行执行不同的任务,被认为是一个计算资源的集合。进程不能被任务是一个应用,因为有些应用有多个进程组成。
2,并行与并发:
并发:单核cpu运行多线程时,在一个时间段内,多个线程交叉运行,给人感觉是同时运行。
并行:多核cpu运行 多线程时,真正的在同一时刻运行。
所以如果是单核多线程并发执行,并不需要处理多线程的同步问题。
3,线程同步和线程安全:
线程同步: 多个并行执行的线程在同一时刻不会执行指定的程序段。
线程安全:在多线程并行运行环境下而不会引发数据错误。线程安全问题都是由“全局变量及静态变量”引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
区别:线程同步是解决线程安全的方法之一。
4,多线程的优点和缺点:
1,有写耗时的任务放在后台运行,不用等待。
2,一些等待的任务,比如用户输入,文件读取和网络收发数据,不用等待完成。
3,整体效率更高
缺点:线程切换消耗,线程的同步控制,更多的线程空间。
5,线程池技术:
线程池技术并不是实现线程同步,或者实现线程安全的技术。只是解决了大量线程的重建的消耗,池中保持一定数量的线程,即时开始处理任务。它是一种实现高并发的方案。比如说在Reactor多线程模型中使用了线程池技术。
6,JVM并发编程中的3个概念:
6.1,原子性
可认为时不可切分的操作,可以说一个指令,也可以有序的多个指令。要么完全执行成功,要么失败回退。
注意:一条Java语句,并不等同于一个原子性指令。
6.2,可见性
举例有三个线程并发,A , B , C线程, 访问同一个变量时,A线程修改了这个变量的值,B, C线程能够立即看得到修改的值。
6.3,有序性
程序执行的顺序按照代码的先后顺序执行。 JVM 内部对程序的有序性做了一些规范,能保证程序运行有序性。
但是存在指令重排的问题。一般来说,处理器为了提高程序运行效率,可能会对输入代码进行优化,最终会保证结果的一致性。
以上3个因素发生变化,都会影响到“线程同步”问题。以下同步解决方案中,都是为了解决这3个概念中的问题。
二,线程安全的技术方案
2.1 volatile变量技术:
作用于被操作的共享变量对象上。解决可见性问题。当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。而普通变量不能保证完全的可见性。
2.1.1作用:
1,保证了可见性
2,避免了JVM的指令重排序
缺点是volatile也无法保证对变量的任何操作都是原子性的。不能形成内存栅栏。
2.1.2 使用场景:
volatile关键字用于声明简单类型变量,如int、float、boolean等数据类型。如果这些简单数据类型声明为volatile,对它们的操作就会变成原子级别的。
原因是声明为volatile的简单变量,如果当前值由该变量以前的值相关,那么volatile关键字不起作用,也就是说如下的表达式都不是原子操作:
n = n + 1;
n++;
//作为标记状态量
volatile boolean flag = false;
//线程1
while(!flag){ //默认情况执行
doSomething();
}
//线程2
public void setFlag() { //线程2执行后,线程1停止
flag = true; //对于变量的操作时原子性的,不依赖于前值。
}
实现功能;实现了并发运行,以及对2个线程的切换。
1, 默认,线程1执行, 线程2未运行。
2,线程2运行, 线程1停止。
2.2 synchronized技术 (Java的线程同步技术之一)
在并发编程中,解决了原子性问题, 作用于多个线程的共同代码上。某个线程修改共享资源的时候,其他线程不能修改该资源,等待修改完毕同步之后,才能去抢夺CPU资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的现象。
2.2.1 同步方法;
可理解为对“该方法所属的对象加锁” ,一次只能一个线程拿到锁,开始执行。也就是说,在同一个时间片内,并发执行的线程(同时执行到当前方法的线程)只有一个可以运行,其他线程必须等待。所以一般效率比较低。
2.2.2 同步代码块:
同步代码块锁定的代码范围更小,多个并发线程更大灵活性更大,毕竟代码块外部的指令依旧可以执行。而且锁定代码块也会更快执行完成。
2.3 CAS机制:属于乐观锁机制,适合并发冲突比较少的场合。
线程A 操作共享变量后,假定其他线程并没有操作该变量。则直接放回该变量。 如果内存的值不是预期值(也即是其他线程操作了该变量,虽然概率很低),则不做任何改变,返回 false。
2.3.1 机制说明:
1,操作变量后,和主内存对比, 失败后,一般开启继续重试。
2,重试的过程是,继续循环过程1,继续拉去新的共享变量,操作然后重新对比。 可理解为,总能找都一个时间片,没有存在并发冲突。
CAS 是 compare and swap 的简写,即比较并交换。它是指一种操作机制,而不是某个具体的类或方法。在 Java 平台上对这种操作进行了包装。在 Unsafe 类中,调用代码如下
unsafe.compareAndSwapInt(this, valueOffset, expect, update);
作为一种底层机制, CAS 在 Java 的原子类和并发包中有大量使用。
CAS 底层是靠调用 CPU 指令集的 cmpxchg 完成的。但是这种方案容易引发一个典型问题:ABA 问题 ,可以通过加版本号解决。
2.4 等待唤醒机制:或者称为“线程通信模型”
wait/notify 就是线程间的一种协作机制。
2.5 Atomic : 原子类
2. 6 Lock锁机制:
AQS是AbstractQueuedSynchronizer的缩写)
(后续补充, 还有补充代码实践演示。)
三, 其他一些相关概念:
悲观锁和乐观锁概念:
悲观锁:和独占锁是一个意思,它假设一定会发生冲突,因此获取到锁之后会阻塞其他等待线程。典型方案是synchronized方案
乐观锁:假设不会产生冲突,先去尝试执行某项操作,失败了再进行其他处理(一般都是不断循环重试),适用于同步冲突比较少的场合。典型方案是CAS方案
公平锁和非公平锁:
公平锁是指各个线程在加锁前先检查有无排队的线程,按排队顺序去获得锁。
非公平锁是指线程加锁前不考虑排队问题,直接尝试获取锁,获取不到再去队尾排队。
可重入锁和不可重入锁:
如果一个线程已经获取到了一个锁,那么它可以访问被这个锁锁住的所有代码块。不可重入锁与之相反。
死锁概念:两个线程相互等待的状态就形成了死锁。