Java多线程及并发
- 1:为什么需要多线程
- 优点
- 缺点
- 1、慢,切换上下文典型值1us vs 0.3ns/cycle
- 能不能让上下文切换尽可能少? 可以协程
- 2、占用资源:每个线程有独立的方法栈。
- 2:Thread是什么?
- 3、多线程反直觉示例
- 示例1
- 示例2:
- 示例3:
- 示例4:异常处理
- 4、Thread 的底层模型
- 5、Thread的生命周期
- 6、ThreadLoacl
- 模拟一个ThreadLocal
- ThreadLocal:线程局部私有的变量
- 7、协程的优缺点
1:为什么需要多线程
- CPU、内存、IO的巨大性能差异
- 多核心CPU的发展
- 线程的本质是一个可以执行代码工人
- 优点:多个执行流,并行执行
- 缺点:
1、慢,切换上下文典型值1us vs 0.3ns/cycle
2、占用资源:每个线程有独立的方法栈。
故:大多数时间CPU都都在等待。多核心CPU,CPU闲置更多。
优点
多个执行流,并行执行
缺点
1、慢,切换上下文典型值1us vs 0.3ns/cycle
CPU在执行A任务(A没有执行完)时,切换到任务B,需要保存A的上下文内容,等待CPU切换到执行A任务使用。需要消耗大概1000个时钟周期。
不是说多线程是可以提高效率,怎么又说多线程慢呢。这里慢单纯指的是线程之间切换时消耗的时间和CPU的时钟周期相比慢。针对内存寻址、硬盘寻址是有提升的。
能不能让上下文切换尽可能少? 可以协程
协程–>用户态线程。需要重写调度器。
2、占用资源:每个线程有独立的方法栈。
每个线程有独立的方法栈。
2:Thread是什么?
- Thread类的每一个实例代表一个JVM中的线程。start()之后,且未结束。
- Runable、Callable都不是线程。
- Thread.start()后,JVM中增加:一个执行流;一套方法栈。
- 不同的执行流的同步执行时一切线程问题的根源。
查看一下Thread 和 runable callable等穿件线程方法类或接口的注释,就清晰说明只有Thread实例才代表JVM中的线程。
3、多线程反直觉示例
示例1
代码 while (true) { i++;} 会在Thread线程和主线程中同时访问变量i。
public static int i =0;
public static void main(String[] args) {
//Thread线程
new Thread(() -> {
while (true) {
i++;
}
}).start();
//主线程
while (true) {
i++;
}
}
示例2:
doSomething到底在哪个线程执行?
答案是不确定。doSomething不是线程,仅仅是个任务。哪个线程执行取决于它的实现。假如线程池满了,可以让调用的线程执行。
public class ThreadTest {
static ExecutorService threadPool = Executors.newCachedThreadPool();
static int i =0;
public static void main(String[] args) {
threadPool.submit(()->{
//doSomething到底在哪个线程执行? 答案是不确定。取决于它的实现。
//假如线程池满了,可以让调用的线程执行。
doSomething();
});
doSomething();
}
private static void doSomething(){
i++;
}
}
示例3:
线程Thread和主线程同时在执行doSomething。
如果doSomething更复杂一点呢。
你脑袋里面时刻要想象两个线程同时在执行,执行到哪儿。
public class ThreadTest02 {
static int i = 0;
public static void main(String[] args) {
//Thread线程
new Thread(()->{
doSomething();
}).start();
//主线程
doSomething();
}
private static void doSomething(){
i++;
}
}
示例4:异常处理
线程的异常只会抛出到它自己的方法栈中,不能夸方法栈抛异常。
主线程中的catch只能捕获主线程中抛出的异常。
public class ThreadTest04 {
static int i = 0;
public static void main(String[] args) {
try {
new Thread(() -> {
//该异常只会抛出到它自己的方法栈中,不能夸方法栈抛异常
throw new RuntimeException();
}).start();
} catch (Exception e) {
//catch只能捕获主线程中抛出的异常。
e.printStackTrace();
}
doSomething();
}
private static void doSomething() {
i++;
}
}
4、Thread 的底层模型
- Thread类的每一个实例代表一个JVM的线程。
在linux上称为<轻量级进程> 和进程无本质区别。
在windos上使用系统线程。 - 优点:
简单,直接依赖操作系统的调度器。 - 缺点:
占用资源多;上下文切换慢,不灵活,无法实现灵活的优先级。
5、Thread的生命周期
public static enum State {
/**
* A Thread which has not yet started.
*/
NEW,
/**
* A Thread which is running or suspended.
*/
RUNNABLE,
/**
* A Thread which is blocked on a monitor.
*/
BLOCKED,
/**
* A Thread which is waiting with no timeout.
*/
WAITING,
/**
* A Thread which is waiting with a timeout.
*/
TIMED_WAITING,
/**
* A thread which is no longer alive.
*/
TERMINATED }
6、ThreadLoacl
- 同一个对象根据调用线程的不同 返回不同的值。
- 通常用于存储线程私有的值,方便后续流程使用。
模拟一个ThreadLocal
调用线程的不同 返回不同的值。主线程和Thread线程调用返回不同的值。
public class ThreadTest05 {
static MyThreadLocal threadLocal =new MyThreadLocal();
public static void main(String[] args) {
threadLocal.set("main主线程");
new Thread(()->{
threadLocal.set("Thread线程");
doSomething();
}).start();
doSomething();
}
private static void doSomething(){
System.out.println("Thread:"+Thread.currentThread().getName()+":"+threadLocal.get());
}
private static class MyThreadLocal{
Map<Long,String> data =new HashMap<>();
public String get(){
return data.get(Thread.currentThread().getId());
}
public void set(String userName){
data.put(Thread.currentThread().getId(), userName);
}
}
}
输出:
Thread:main:main主线程
Thread:Thread-2:Thread线程
ThreadLocal:线程局部私有的变量
模拟登录存储用户信息,使用拦截器,调用set方法即可。
ThreadLocal中没有上面MyThreadLocal时的map对象,那么它的值存在什么地方?存在ThreadLocalMap里面。
/**
* 当前登录用户的信息 使用拦截器
*/
static class UserContext{
static ThreadLocal<Integer> threadLocal =new ThreadLocal<>();
public void set (Integer userId){
threadLocal.set(userId);
}
public Integer get(){
return threadLocal.get();
}
}
7、协程的优缺点
操作系统内核线程的缺点:
- 慢:上下文切换耗费时间长(1000个cycles)。调度的时候需要发起系统调用,在内核态和用户态之间切换。
- 大:独立的方法栈需要更多的空间。
携程:
- 快:始终占用CPU,在用户态。
- 小:可以方便的实现上百万的并发度。
携程解决的问题:多线程调度慢,占用资源多大额问题。
携程未解决的问题:并发问题,死锁,竞争条件。