01 Java多线程及并发 为什么需要多线程;多线程的本质问题;ThreadLocal

1:为什么需要多线程

  • CPU、内存、IO的巨大性能差异
  • 多核心CPU的发展
  • 线程的本质是一个可以执行代码工人
  • 优点:多个执行流,并行执行
  • 缺点:

    1、慢,切换上下文典型值1us vs 0.3ns/cycle

    2、占用资源:每个线程有独立的方法栈。

故:大多数时间CPU都都在等待。多核心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中的线程。
Thread

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的生命周期

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,在用户态。
  • 小:可以方便的实现上百万的并发度。

携程解决的问题:多线程调度慢,占用资源多大额问题。
携程未解决的问题:并发问题,死锁,竞争条件。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

EngineerForSoul

你的鼓励是我孜孜不倦的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值