文章目录
文章目录
- 1、JAVA高性能编程——多线程并发编程基础
- 1.1 Java程序运行原理分析
- 1.1.1线程独占
- 1.1.2 线程共享
- 1.1.3 方法区
- 1.1.4 堆
- 1.1.5 虚拟机栈
- 1.1.6 栈帧
- 1.1.7 本地方法栈
- 1.1.8 程序计数器
- 1.2 线程状态
- 1.2.1 New
- 1.2.2 Runnable
- 1.2.3 Blocked
- 1.2.4 Waiting
- 1.2.5 Timed Waiting
- 1.2.6 Terminated
- 1.3 线程中止
- 1.3.2 Destory
- 1.3.3 Interrupt
- 1.3.4 标志位
- 1.4 内存屏障和CPU缓存
- 1.5 线程通信
- 1.5.2 wait 与 notify/notifyAll
- 1.5.3 park 与 unpark机制
- 1.5.4 伪唤醒
- 1.6 线程封闭之ThreadLocal和栈封闭
- 1.7 线程池应用及实现原理剖析
- 1.7.2 工作线程
- 1.7.3 任务接口
- 1.7.4 任务队列
- 1.7.5 线程池API-接口定义和实现类
1、JAVA高性能编程——多线程并发编程基础
1.1 Java程序运行原理分析
JVM运行时数据区图解
1.1.1线程独占
每个线程都会有它独立的空间,随线程生命周期而创建和销毁【虚拟机栈、本地方法栈、程序计数器】
1.1.2 线程共享
所有线程能够能够访问这块内存数据,随着虚拟机或者GC而创建和销毁
1.1.3 方法区
JVM用来存储加载的类信息,常量,静态变量,编译后的代码等数据。这是一块逻辑区划,不同虚拟机有不同实现。
1.1.4 堆
堆内存还可以细分为:老年代,新生代(Eden, From Survivor, To Survivor)JVM启动时创建,存放对象的实例。垃圾回收器主要就是管理堆内存。内存溢出OOM就是指堆内存满了。
1.1.5 虚拟机栈
每个线程都在这个空间有一个私有的空间。线程栈由多个栈帧(Stack Frame)组成。一个线程会执行一个或多个方法,一个方法对应一个栈帧。
1.1.6 栈帧
栈帧内容包含:局部变量表、操作数栈、动态链接、方法返回地址、附加信息等。栈内存默认最大时1M,超出则抛出StackOverflowError。
1.1.7 本地方法栈
和虚拟机栈功能类似,虚拟机栈是为虚拟机执行JAVA方法而准备的,本地方法栈是为虚拟机使用Native本地方法而准备的。
1.1.8 程序计数器
程序计数器(Program Counter Register)记录当前线程执行字节码的位置,存储的是字节码指令地址,如果执行Native方法,则计数器值为空。
每个线程都在这个空间有一个私有的空间,占用内存空间很少。
CPU同一时间,只会执行一条线程中的指令,JVM多线程会轮流切换并分配CPU执行时间的方法。为了线程切换后,需要通过程序计数器来恢复正确的执行位置。
1.2 线程状态
6个状态定义:java.lang.Thread.State
线程状态切换图解:
1.2.1 New
尚未启动的线程的线程状态。
1.2.2 Runnable
可运行线程的线程状态,等待CPU调度。
1.2.3 Blocked
线程阻塞等待监视器锁定的线程状态。处于synchronized同步代码块或方法中被阻塞。
1.2.4 Waiting
等待线程的线程状态。下列不带超时的方式:
Object.wait、Thread.join、LockSuppot.park
1.2.5 Timed Waiting
具有指定等待时间的等待线程的线程状态。下列带超时的方式:
Thread.sleep、Object.wait、Thread.join、LockSupport.parkNanos、LockSupport.parkUntil
1.2.6 Terminated
终止线程的线程状态。线程正常完成执行或者出现异常。
1.3 线程中止
##### 1.3.1 Stop
Stop: 中止线程,并且清楚监控器锁的信息,但是可能导致线程安全问题,JDK不建议用。
1.3.2 Destory
JDK未实现该方法。
1.3.3 Interrupt
如果目标线程在调用Object class的wait()、wait(long)或wait(long, int)方法、join()、join(long, int)或sleep(long, int)方法时被阻塞,那么Interrupt会生效,该线程的中断状态将被清除,抛出InterruptedException异常。
如果目标线程是被I/O或者NIO中的Channel所阻塞,同样,I/O操作会被中断或返回特殊异常值。达到终止线程的目的的。
如果以上条件都不满足,则会设置此线程的中断状态。
Interrupt示例代码:
// 声明一个方法,继承Thread,并重写run方法。
public class StopThread extends Thread {
private int i = 0, j = 0;
@Override
public void run() {
synchronized (this) {
// 增加同步锁,确保线程安全
++i;
try {
// 休眠10秒,模拟耗时操作
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
++j;
}
}
/** * 打印i和j */
public void print() {
System.out.println("i=" + i + " j=" + j);
}
}
public static void main(String[] args) throws InterruptedException {
StopThread thread = new StopThread();
thread.start();
// 休眠1秒,确保i变量自增成功
Thread.sleep(1000);
// 暂停线程
// thread.stop(); // 错误的终止
thread.interrupt(); // 正确终止
while (thread.isAlive()) {
// 确保线程已经终止
} // 输出结果
thread.print();
}
运行结果:
1.3.4 标志位
如果代码逻辑中是循环执行的业务,可以通过设置while条件来实现线程中止。如下:
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
try {
while (flag) { // 判断是否运行
System.out.println("运行中");
Thread.sleep(1000L);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
// 3秒之后,将状态标志改为False,代表不继续运行
Thread.sleep(3000L);
flag = false;
System.out.println("程序运行结束");
}
1.4 内存屏障和CPU缓存
Cpu缓存分为: L1 L2 L3
L1 与 L2 为cpu独占缓存。
L3为公共缓存。
Cpu在读取数据的时候,查询顺序是:L1,L2, L3,内存,外存储器。
Cpu指令重排:也是cpu性能优化的一种,即cpu把程序运行顺序重排,先执行耗时短的,后执行耗时长的,但最终结果与正常一致。(多线程情况下)
由于多核cpu独立运行,并拥有独自的cpu缓存,则在多线程工作情况下,容易造成数据不一致的问题。即:缓存中的数据与主内存的数据并非实时同步,各CPU之间的缓存数据也不是实时同步。
CPU指令重排仅在单CPU自己执行的情况下能保证结果正确,多核多线程中,指令逻辑无法分辨因果关联,可能出现乱序执行,导致程序运行结果错误。
为了解决以上2个问题,提出了内存屏障的概念。即cpu厂商提供了两个指令:
##### 1.4.1 写内存屏障(Store Memory Barrier)
在指令后插入Store Barrier,能让写入缓存中的最新数据更新写入主内存,让其他线程可见。这种情况,CPU不会因为性能考虑去对指令重排。
##### 1.4.2 读内存屏障(Load Memory Barrier)
在指令前插入Load Barrier , 可以让高速缓存中的数据失效,强制从主内存加载最新数据。强制读取主内存中的数据,让CPU缓存与主内存保持一致,避免了缓存导致的一致性问题。
1.5 线程通信
多线程之间的通信,类似于生产者线程,消费者线程。分为三种方法:
##### 1.5.1 suspend
suspend挂起目标线程,通过resume可以恢复线程执行。已经被java给废弃了,缺点是使用条件比较苛刻,必须同时满足1 没有锁的情况, 2 保证线程执行顺序。否则,会出现1 线程安全问题,和2 死锁问题。
1.5.2 wait 与 notify/notifyAll
Wait方法导致当前线程等待,加入对象的等待集合中(每个对象都有等待集合,默认是Null),并且放弃当前持有的对象锁。 Notify/notifyAll方法唤醒一个或所有正在等待这个对象锁的线程。缺点:必须保证线程执行顺序。但不存在线程安全问题。
1.5.3 park 与 unpark机制
线程调用park则等待‘许可’,unpark方法为指定线程提供‘许可(permit)’。不存在顺序问题。缺点:同步代码容易写出死锁情况。
1.5.4 伪唤醒
多线程不要通过if语句来判断,java底层容易出现为唤醒情况。所以最好通过while语句来判断。
1.6 线程封闭之ThreadLocal和栈封闭
多线程访问共享可变数据时,涉及到线程之间数据同步的问题。并不是所有的时候,都需要用到共享数据,所以线程封闭概念就提出来了。数据都被封闭在各自的线程之中,就不需要同步,这种通过将线程数据封闭在各自的线程中,避免了线程同步问题。这种技术叫做:线程封闭。
线程封闭具体的体现有:ThreadLocal 局部变量。
1.7 线程池应用及实现原理剖析
线程池中线程执行流程:核心线程数–>线程队列–>最大线程数–>线程池拒绝策略。
##### 1.7.1 线程池管理器
用于创建并管理线程池,包括创建线程池,销毁线程池,添加新任务。
1.7.2 工作线程
线程池中线程,在没有任务时处于等待状态,可以循环的执行任务。
1.7.3 任务接口
每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等。
1.7.4 任务队列
用于存放没有处理的任务。提供一种缓冲机制。
1.7.5 线程池API-接口定义和实现类