文章目录


文章目录

  • 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运行时数据区图解

高并发高可用Java 编程 java高并发编程指南_高并发高可用Java 编程

1.1.1线程独占

每个线程都会有它独立的空间,随线程生命周期而创建和销毁【虚拟机栈、本地方法栈、程序计数器】

1.1.2 线程共享

所有线程能够能够访问这块内存数据,随着虚拟机或者GC而创建和销毁

1.1.3 方法区

JVM用来存储加载的类信息,常量,静态变量,编译后的代码等数据。这是一块逻辑区划,不同虚拟机有不同实现。

1.1.4 堆

堆内存还可以细分为:老年代,新生代(Eden, From Survivor, To Survivor)JVM启动时创建,存放对象的实例。垃圾回收器主要就是管理堆内存。内存溢出OOM就是指堆内存满了。

1.1.5 虚拟机栈

高并发高可用Java 编程 java高并发编程指南_并发编程_02

每个线程都在这个空间有一个私有的空间。线程栈由多个栈帧(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

线程状态切换图解:

高并发高可用Java 编程 java高并发编程指南_线程池_03

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();
}

运行结果:

高并发高可用Java 编程 java高并发编程指南_并发编程_04

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-接口定义和实现类


高并发高可用Java 编程 java高并发编程指南_并发编程_05