并发/高并发
tomcat默认150并发连接(socket)
RT:相应时间
QPS:吞吐量
硬件
cpu,内存,磁盘,网络
软件
最大化使用硬件资源
线程数量、JVM内存大小、网络通信机制(BIO、NIO、AIO)、磁盘IO
线程数量如何提升服务端并发数量
什么是线程
马路车道
线程是cpu执行的最小调度单元
并行线程数量由cpu决定(cpu核心数或者核心数*2)
并发和并行
单核cpu也支持多线程 --> cpu时间片切换
并发:同时发生的请求连接
并行:同时处理执行的请求
多线程的特点
同步
需要阻塞等待其他线程完成处理
异步
不需要阻塞当前的处理
并行
多线程同时执行处理
Java中的线程
- Runnable 借口
- Thread 类
- Callable/Future 带返回值的
public class ThreadDemo extends Thread {
@Override
public void run(){
//线程执行的指令
}
public static void main(String[] args){
ThreadDemo t = new ThreadDemo();
t.start();//启动线程
}
}
public class CallableDemo implements Callable<String> {
@Override
public String call(){
//call()等价于run(),线程执行的指令
return null;
}
public static void main(String[] args){
ExecutorService exe = Executors.newFixedThreadPool(1);
CallableDemo c = new CallableDemo();
Future<String> f = exe.submit(c);
f.get();//get()方法是阻塞的
}
}
如何使用Java线程
- 网络请求分发
- 文件导入
- 短信发送
线程的基础
线程的生命周期
线程的启动 --> 结束
线程状态
阻塞
- TIMED_WAITING
- WAITING
- BLOCKED
public class Demo {
public static void main(String[] args){
//阻塞状态 TIMED_WAITING
new Thread(() -> {
while(true){
try {
TimeUnit.SECONDS.sleep(100);
} catch (InterruptedException e){
e.printStackTrace();
}
}
}).start();
//阻塞状态 WAITING
new Thread(() -> {
while(true) {
synchronized (Demo.class) {
try {
Demo.class.wait();
} catch (InterruptedException e){
e.printStackTrace();
}
}
}
}).start();
//抢占锁 TIMED_WAITING
new Thread(new BlockedDemo()).start();
//未抢占锁 BLOCKED
new Thread(new BlockedDemo()).start();
}
static class BlockedDemo extends Thread {
@Override
public void run() {
while(true) {
synchronized (Demo.class) {
try {
TimeUnit.SECONDS.sleep(100);
} catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
}
}
jps 查看当前线程的Pid
jstack pid 查看pid的堆栈信息
Java中的线程状态:6种
-
new (线程初始化)
-
runnable(cpu OS调度前为就绪状态)
-
waiting
sleep(0);
wait();
join();
LockSupport.parkUnitl();
notify
notifyAll
unpark
-
timed_waiting
sleep(long);
wait(long);
join(long);
LockSupport.parkUnitl(xx);
notify
notifyAll
unpark
-
blocked (锁阻塞状态)
-
终止 (线程运行结束)
操作系统层面的线程状态:5种
没有new状态
线程的启动
new Thread().start();//启动线程
new Thread().run();//调用实例方法
-
Thread.start方法调用本地方法start0,启动线程
-
JVM通过本地方法start0调用不同系统层面的创建线程
-
OS调用JVM的Thread.run方法(OS通过CPU调度算法调用CPU执行run方法指令)
-
JVM调用Java中Thread.run
-
执行结束之后JVM销毁线程
线程终止
线程什么情况下终止
run方法执行结束
Thread.stop方法强制终止 ,不建议使用
发送终止信号通知终止,interrupt
Thread.currentThread().isInterrupted() 表示中断标记,默认是false
Thread.interrupt(); 设置interrupt = true
Thread.interrupted(); 复位,恢复interrupt = false
interrupt(); --> 通过一个共享变量实现线程之间的通信
设置一个共享变量的值 true 。volatile jint _interrupted;
唤醒处于阻塞状态的线程
线程安全
问题
public static int count = 0;
public static incr() {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
count ++;
}
public static void main(String[] args) {
for(int i = 0; i < 1000; i ++) {
new Thread(APP::incr).start();
}
try {
Thread.sleep(1000);
} catch(InterruptedException e) {
e.printStackTrace();
}
}
结果是小于等于1000的随机数
线程安全问题
- 原子性
- 有序性
- 可见性
cpu时间片切换导致数据安全问题
问题出现 count ++,不是原子操作,编译成三个指令:
- 加载到寄存器
- 累加
- 写入内存
锁(Synchronized)
public class SynchronizedDemo {
synchronized void demo1(){}//方法
synchronized static void demo2(){}//方法
Object o = new Object();
void demo3() {
synchronized(o) {
//代码块
}
}
}
//锁的范围
实例锁/对象锁
锁的范围控制在对象实例内
SynchronizedDemo demo = new SynchronizedDemo();
new Thread(() -> {
demo.demo1();
}).start();
new Thread(() -> {
demo.demo1();
}).start();
void demo() {
synchronized(this) {
//代码块
}
}
类锁
静态方法,类对象,类锁
synchronized static void demo(){}
void demo() {
synchronized(SynchronizedDemo.class) {
//代码块
}
}
互斥锁的本质
共享资源
通过抢占共享资源实现排他性
锁的存储(对象头)
对象在堆中的布局 -> oop.hpp ->oopDesc
对象标记 markOop _mark
类元信息 markOop _mark
实例数据 _metadata
[对齐填充]
对象标记(markOop.hpp)
hashcode
分代年龄
同步锁标记
偏向锁标记
偏向锁持有者线程ID
monitor() -> ObjectMonitor 争抢锁的实现逻辑
jol 查看类对象头信息布局的工具
-XX:-UseCompressedOops 关闭JVM对象头压缩指针
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.10</version>
</dependency>
System.out.println(ClassLayout.parseInstance(instance).toPrintable());
偏向锁,一次CAS替换对象头线程ID,默认关闭
轻量级锁,多次CAS替换mark word中的指向栈桢的指针,自旋锁,1.6以前默认自选10次,1.6以后自适应自旋锁
重量级锁,线程阻塞
乐观锁
CAS 比较预期数据和原始数据是否一致,如果一致则修改,不一致则修改失败
对象监视器
monitor,重量级锁实现
线程的通信(wait/notify)
生产者 – 消费者
//消费者
public class Consumer implements Runnable {
private Queue<String> msg;
private int maxSize;
public Consumer(Queue<String> msg, int maxSize) {
this.msg = msg;
this.maxSize = maxSize;
}
@Override
public void run() {
while(true) {
synchronized(msg) {
while(msg.isEmpty()) {//队列为空,无法消费,阻塞
try {
msg.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费者消费消息:" + msg.ramove());
msg.notify();//唤醒生产者生产
}
}
}
}
//生产者
public class Producer implements Runnable {
private Queue<String> msg;
private int maxSize;
public Producer(Queue<String> msg, int maxSize) {
this.msg = msg;
this.maxSize = maxSize;
}
@Override
public void run() {
int i = 0;
while(true) {
i ++;
synchronized(msg) {
while(msg.size() == maxSize) {//队列满了,不生产,阻塞
try {
msg.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("生产者生产消息:消息" + i);
msg.add("消息" + i);
msg.notify();//唤醒消费者消费
}
}
}
}
public class App {
public static void main() {
Queue<String> msg = new LinkedList<>();
int maxSize = 10;
new Thread(new Producer(msg, maxSize)).start();
new Thread(new Consumer(msg, maxSize)).start();
}
}
线程的死锁
什么是死锁
一组相互竞争共享资源的线程之间相互等待,导致永久性阻塞
活锁 线程没有出现阻塞并一直循环的状态
死锁产生的条件(同时满足)
- 互斥,共享资源X和Y只能被一个线程占用
- 占有且等待,线程1已经取得共享资源X,在等待共享资源Y的时候,不释放共享资源X
- 不可抢占,其他线程不能强行抢占线程1占有的资源
- 循环等待,线程1等待线程2占有的资源,线程2等待线程1占有的资源
通过扩大锁的粒度破坏占有且等待
通过ReentrantLock.tryLock()破坏不可抢占
通过顺序加锁破坏循环等待
ThreadLocal
线程隔离机制 ,线程独有的
每个线程都持有一个ThreadLocalMap
0x61c88647 魔数,黄金分割,斐波那契散列
线性探索 -> 用来解决hash冲突的一种策略
- 写入,找到发生冲突最近的空闲单元
- 查找,从发生冲突的地方往后查找
private static final int HASH_INCREMENT = 0x61c88647;
public static void main() {
magicHash(16);
}
public static void magicHash(int size) {
int hashCode = 0;
for (int i = 0; i < size; i ++) {
hashCode = i * HASH_INCREMENT + HASH_INCREMENT;
System.out.print((hashCode & (size - 1)) + " ");
}
}
线程安全性–可见性(volatile)
问题
public class VolatileDemo {
public static boolean stop = false;
//public static volatile boolean stop = false;
//public static volatile int i;
public static void main() {
new Thread(() -> {
int i = 0;
while(!stop) {
i ++;
//System.out.println("rs:" + i); 可以结束循环
//try {
// Thread.sleep(0); 可以结束循环
//} catch (InterruptedException e) {
// e.printStackTrace();
//}
}
System.out.println("rs:" + i);
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
stop = true;
}
}
无法停止线程,不能输出 rs + i
volatile 关键字修饰可以解决可见性问题
-Djava.compiler=NONE 关闭即时编译器
server版本的jdk做了深度优化(JIT) --just in time
while(!stop){
i ++;
}
//优化后
if(!stop) {
while(true) {
i ++;
}
}
print可以导致循环结束
活性失败
print中包含synchronized修饰
锁的释放会强制把工作内存的写操作同步到主内存
print是IO操作
IO操作会阻塞线程
Thread.sleep()可以结束循坏
官方说明 编译器可以自由的一次或者多次加载this.done
sleep会导致线程切换,致使缓存失效,重新加载
volatile关键字
hsdis 打印汇编指令的工具
lock汇编指令–保证可见性
lock指令出发cpu核心加载缓存操作
什么是可见性问题
线程A修改变量之后对线程B不可见
硬件层面
cpu、内存、IO设备
速度 cpu > 内存 > IO
- cpu增加高速缓存
- 操作系统,进程、线程、cpu时间片切换
- 编译器的优化,更合理的利用cpu的高速缓存
cpu高速缓存
- L1d 数据缓存,cpu核心独占
- L1i 指令缓存,cpu核心独占
- L2 cpu核心独占
- L3 共享
因为高速缓存的存在,导致缓存一致性问题
总线锁
缓存锁
cpu架构是否支持
当前数据是否存在于缓存行
缓存一致性协议
MSI、MESI、MOSI…
MESI表示四种缓存状态
- M modify 修改
- E exclusive 独占
- S shared 共享
- I invalid 失效
Store Buffer
引入store buffer解决cpu通知缓存失效的阻塞
引入store buffer导致指令重排序
内存屏障
不允许指令重排序,并写入主内存
volatile 关键字通过内存屏障禁止指令重排序
- 读屏障 #Load 指令
- 写屏障 #Store 指令
- 全屏障 #Fence 指令
防止指令重排序
禁止cpu高速缓存
#Lock指令 -> 等价于内存屏障
不同cpu架构,X86是强一致性架构
软件层面
Java内存模型(JMM)
内存模型定义了共享内存中多线程读写的操作规范
提供可见性和有序性问题的解决方案
JMM基于硬件层面封装 内存屏障指令类型
- LoadLoad Barriers 顺序加载 Load1;LoadLoad;Load2
- LoadStore Barriers 先读再写 Load1;LoadStore;Store2
- StoreStore Barriers 顺序写入 Store1;StoreStore;Store2
- StoreLoad Barriers 先写再读 Store1;StoreLoad;Load2
javap -v Xx.class 查看字节指令
ACC_VOLATILE
bytecodeInterpreter.cpp
orderAccess.hpp
Happens-Before模型
程序顺序规则(as-if-serial语义)
-
不能改变程序的执行结果(在单线程环境下,执行的结果不变)
-
依赖问题,如果两个指令存在依赖关系,不允许重排序
void test() { int a = 1; int b = 1; int c = a * b; }
a happens-before b ; b happens-before c
传递性规则
a happens-before b ; b happens-before c --> a happens-before c
volatile变量规则
volatile变量的写操作一定happens-before后续对于volatile变量的读操作
通过内存屏障防止指令重排序
public class VolatileExample{
int a = 0;
volatile boolean flag = false;
//Thread1
public void writer() {
a = 1; // 1
flag = true; //修改 2
}
//Thread2
public void reader() {
if (flag) { //flag = true 3
int i = a; //i = 1 4
}
}
}
1 happens-before 2 是否成立?是
3 happens-before 4 是否成立?是
2 happens-before 3 --> volatile规则
1 happens-before 4 i = 1 成立
监视器锁规则
int x = 10;
synchronized(this) {
//后续线程读取到的x的值一定是12
if (x < 12) {
x = 12;
}
}
锁的释放一定happens-before后续对于线程加锁的操作
start规则
public class StartDemo {
int x = 0;
Thread t = new Thread(() -> {
//读取x的值一定是20
if (x == 20){} //一定是成立
});
x = 20;
t.start();
}
线程start之前的操作一定happens-before线程run操作
Join规则
public class JionDemo {
public static void main() {
int x = 0;
Thread t = new Thread(() -> {
x = 20;
});
t.start();
t.jion();//保证结果的可见性
//在jion之后读取到的x的值一定是20
}
}
t线程中对共享变量的修改对于所有jion之后的操作可见
wait/notify,Thread实例对象锁
fatally关键字提供了内存屏障的规则