线程是什么

5 minute read

  多线程并发线程需要靠多个线程不停地压榨CPU来提高处理能力,所以了解和熟悉线程是多线程并发编程的重要基础知识。

线程基础

  进程是系统进行资源分配和调度的基本单位,一个进程中至少有一个线程,进程中的多个线程共享进程的资源,线程是CPU分配的基本单位。
  在Java中,启动main函数其实就是启动了一个JVM进程,main函数所在的线程就是进程中的一个线程,也就是主线程。
  线程是CPU执行的基本单位,而CPU一般是使用时间片轮转方式让线程轮询占用的,当前线程CPU时间片用完后,要让出CPU,等到下次轮到自己的时候再执行。 如何知道之前程序执行到哪?程序计数器是为了记录该线程让出CPU的执行地址,待再次分配到时间片时,线程可以从自己私有的计数器指定地址继续运行。 如果执行的是native方法,程序计数器记录的是undefined地址;执行的是Java代码时,记录的是下一条指令的地址。
  线程调用start方法后并没有马上执行,而是出于就绪状态,就绪状态是指线程已经获取了除CPU资源外的其它资源,等待获取CPU资源后才会真正处于运行状态。

线程上下文切换

  线程进行上下文切换时,需要保存当前线程的执行现场,再次执行时根据保存的现场恢复执行现场。
  线程上下文切换时机有:
  1. 当前线程CPU时间使用完
  2. 当前线程阻塞挂起或被其它线程中断

守护线程和用户线程

  Java中的线程分为两类,分别为daemon线程(守护线程)和user线程(用户线程)。JVM启动时会调用main函数,main函数所在的线程就是一个用户线程,不过JVM启动的时候还会启动很多守护线程,比如垃圾回收线程。
  用户线程和守护线程的区别在于,当最后一个非守护线程结束时,JVM会正常退出,而不管当前是否有守护线程,就是说有一个用户线程还没结束,正常情况下JVM就不会退出。
  thread.setDaemon(true)
  main线程运行结束后,JVM会自动启动一个叫做DestroyJavaVM的线程,该线程会等待所有用户线程结束后终止JVM进程。
  在Tomcat的NIO实现中,NioEndpoint中会开启一组接受线程来接受用户的连接请求,以及一组处理线程负责具体处理用户请求。在默认情况下,接受线程和处理线程都是守护线程,这意味着当tomcat收到shutdown命令后且 没有其它用户线程存在的情况下,tomcat进程会马上消亡,不会等待处理线程处理完当前的请求。
参考
《并发编程之美》

线程之间共享哪些资源? a. 堆 由于堆是在进程空间中开辟出来的,所以它是理所当然地被共享的;因此new出来的都是共享的(16位平台上分全局堆和局部堆,局部堆是独享的) b. 全局变量 它是与具体某一函数无关的,所以也与特定线程无关;因此也是共享的 c. 静态变量 虽然对于局部变量来说,它在代码中是“放”在某一函数中的,但是其存放位置和全局变量一样,存于堆中开辟的.bss和.data段,是共享的 d. 文件等公用资源 这个是共享的,使用这些公共资源的线程必须同步。Win32 提供了几种同步资源的方式,包括信号、临界区、事件和互斥体。 独享的资源有 a. 栈 栈是独享的 b. 寄存器 这个可能会误解,因为电脑的寄存器是物理的,每个线程去取值难道不一样吗?其实线程里存放的是副本,包括程序计数器PC

线程共享的环境包括: 进程代码段、进程的公有数据(利用这些共享的数据,线程很容易的实现相互之间的通讯)、进程打开的文件描述符、信号的处理器、进程的当前目录和进程用户ID与进程组ID。

线程之间通信方式? ① 锁机制 互斥锁、条件变量、读写锁和自旋锁 ② 信号量机制(Semaphore) ④ violate全局变量-共享内存 ⑤ wait/notify

进程之间通信方式? ① 管道(Pipe)及有名管道(named pipe) ② 报文(Message)队列(消息队列) ③ 共享内存 ④ 信号(Signal) ⑤ 信号量(semaphore) ⑥ 套接口(Socket) 给定进程A、B,设计代码使得A在B之前完成? 信号量

三个线程保证顺序执行? https://blog.csdn.net/Evankaka/article/details/80800081

面试题 sleep和wait https://www.cnblogs.com/aspirant/p/8900276.html

ThreadLocal作用? https://blog.csdn.net/weixin_44922510/article/details/103615087 普通的全局变量可以被线程访问并且修改,而threadLocal类型的全局变量能传递局部变量,只被该线程访问,其它线程无法访问和修改 ThreadLocal的左右是保存上下文信息,比如记录某次请求trace id,ThreadLocal的这种用处,很多时候是用在一些优秀的框架里面的,一般我们很少接触,反而下面的场景我们接触的更多一些!

ThreadLocal知识点? https://juejin.cn/post/6844903870997479437#comment ThreadLocal无法解决共享对象的更新问题 一个ThreadLocal对象只能存储一个Object对象,如果需要存储多个,则需要new多个ThreadLocal对象 由于线程的生命周期很长,如果我们往ThreadLocal里面set了很大很大的Object对象,虽然set、get等等方法在特定的条件会调用进行额外的清理,但是ThreadLocal被垃圾回收后,在ThreadLocalMap里对应的Entry的键值会变成null,但是后续在也没有操作set、get等方法了。 所以最佳实践,应该在我们不使用的时候,主动调用remove方法进行清理。

ThreadLocal原理 每个Thread对象有一个ThreadLocal.ThreadLocalMap成员变量,ThreadLocalMap中key是继承weakreferance的弱对象ThreadLocal对象,value是threadLocal设置的值 所以,其实每个线程中的ThreadLocal对象使用的是自己的成员变量,即为这个线程私有,不存在多线程竞争的问题,其实是一种拿空间换时间的一种思想

ThreadLocal是否存在内存泄漏问题? 只要线程没有推出,线程到value的引用链就会存在,注意value的引用链是当前线程,而不是threadLocal对象,所以对于线程池中core常驻线程,ThreadLocalMap中 Entry对象不会被垃圾回收。 所以jdk进行了优化,当调用threadLocal的set/get方法时,如果key为null,则消除entry对象。

hash碰撞问题 threadLocalMap底层是一个Map,当线程使用了多个ThreadLocal对象,则线程私有的这个threadLocalMap对象中会有两个元素,难免会有 hash冲突的问题,解决冲突的方式是线性探测的方式,效率很低,所以注意ThreadLocal不要放入太多的对象

ThreadLocal的优点? 没有锁的机制,没有锁的开销

补充说明: FastThreadLocal http://www.jiangxinlingdu.com/interview/2019/07/01/fastthreadlocal.html

ThreadLocal的使用场景是什么? https://www.cnblogs.com/fengzheng/p/8690253.html ThreadLocal 定义的通常是与线程关联的私有静态字段(例如,用户ID或事务ID) https://juejin.cn/post/6858570628900126728 微服务请求的requestId(或traceId),在微服务模块中用于唯一标记一次请求的全局唯一UUID,在不同模块中传递相同的traceid是通过RPC框架封装并且序列化/反序列化实现的, 而RPC框架反序列化出traceid后就会将其放入到ThreadLocal中, 这样在模块的各个阶段记录log时都可以通过该ID标识出该请求。并且随着微服务框架的完成,也可以通过一个服务将traceid串联起来,分析一次请求中各个阶段的状态以及耗时。

使用reentrantlock和condation实现精准控制 private Lock lock1 = new ReentrantLock(); private Condition c1 = lock1.newCondition(); private Condition c2 = lock1.newCondition(); private Condition c3 = lock1.newCondition();

public void print(String name, int target, Condition current, Condition next) throws InterruptedException {
    try {
        lock1.lock();
        while (state % 3 != target) {
            current.await();
        }
        state ++;
        System.out.println(name);
        next.signal();
    } finally {
        lock1.unlock();
    }

}

class ThreadTest implements Runnable {
    private String name;
    private int target;
    private Condition current;
    private Condition next;

    public ThreadTest(String name, int target, Condition current, Condition next) {

    }

    @Override
    public void run() {
        try {
            print(name, target, current, next);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

new ThreadTest("a", 1, c1, c2);
new ThreadTest("b", 2, c2, c3);
new ThreadTest("c", 3, c3, c1);

private Lock lock = new ReentrantLock(); private int state = 0;

private void print(String name, int target) {
    lock.lock();
    if (state == target) {
        System.out.println(name);
        state ++;
    }
    lock.unlock();
}

Updated:

Leave a comment