线程基础知识

线程发展史

线程间通信方式

共享内存,信号量,互斥锁,管道,条件变量

IPC:管道,消息队列,共享内存,信号量,套接字,文件映射

底层角度理解什么是进程

进程是操作系统进行资源分配的基本单位

线程切换

T1把数据放到缓存在切换T2

线程数是不是越大越好?

线程之间的切换也要消耗资源。

创建线程的五种方法

使用继承Runnable实现更合适,因为上面继承了一个类就不能继承别的类

run返回值是void,想自定义返回值

线程状态

synchonized才会blocking,不然都是waiting

僵尸状态

进程有僵尸状态,子进程终止后,父进程未调用wait()或waitpid()回收资源,子进程进入僵尸状态。
线程没有僵尸状态,线程终止后,其资源由进程或线程库自动回收,无需显式调用回收函数

线程打断,设置标志位

 

 

线程中断

不建议使用:stop resume suspended 因为太粗暴了,没有善后,会造成数据不一致

 

volitate interrupd 无法结束的跟精确,比如栈放到第五个元素就结束这个无法做到

三大特性

可见性(sychonized和volitale)

 m end永远无法被打印,因为线程1看不见主线程已经把running改成了false,它还从缓存中读取running,为true

主内存,高速缓存,堆内存,cpu缓存,工作内存

硬件视角

  • 主内存 = 电脑内存条(DDR4/DDR5)
  • CPU三级缓存 = L1/L2/L缓存(苹果M2芯片:L1 192KB/核心,L2 32MB/集群)
  • 高速缓存 = 此处特指CPU缓存体系

2️⃣ JVM内存模型

  • 堆内存 ≈ 主内存中的对象存储区(JDK8前在永久代,现元空间在本地内存)
  • 栈内存 ≈ CPU寄存器的延伸(HotSpot每个线程栈默认1MB,Xss可调)

3️⃣ 线程交互机制 当主线程执行run=true时: ① 修改本地缓存(如L1缓存) ② 通过MESI协议同步到L3缓存 ③ 最终刷回主内存(存在延迟)

而t线程: ① 从自己的L1缓存读取旧值 (线程的高速缓存可以看做是L1缓存的一部分)② 因未触发缓存一致性协议更新 ③ 持续看到run=true(即使主存已更新)

4️⃣ 现实案例对比 volatile变量相当于: ① 强制读写直达L3缓存(lock前缀指令) ② 禁止JVM指令重排序 ③ 类似CPU的memory barrier(如x86的mfence)

❗ 关键矛盾:硬件缓存层级 vs JMM抽象模型 图示的高速缓存既映射CPU物理缓存,也对应JVM工作内存概念,这是理解Java并发问题的双重视角。

 物理存储层级(从底层硬件向上)

CPU寄存器 → L1/L2高速缓存(SRAM) → L3共享缓存 → 主内存(DRAM,含栈内存)→ 磁盘

  • 栈内存属于主内存(DRAM)的一部分,是线程私有的内存区域
  • 高速缓存是CPU内部的高速存储层,缓存来自主内存(包括栈内存)的热点数据

② 数据访问流程(以栈内存变量为例)

Java

void example() { int a = 10; // a存储在栈内存(主内存的某块区域) System.out.println(a); // CPU访问a时的实际路径: }

  1. CPU尝试从L1缓存读取变量a → 缓存命中:直接使用(耗时约1ns) → 缓存未命中:触发缓存行(Cache Line)加载
  2. 从主内存的栈内存区域加载a所在内存块到L3→L2→L1缓存(耗时约100ns)
  3. 后续访问a时直接从高速缓存读取(速度提升100倍)

栈内存的数据一致性由线程隔离保证,高速缓存的一致性由硬件协议保证

工作内存是线程私有区域

总结

Java 内存模型规定所有的共享变量都是存在主存当中(类似于前面说的物理内存),每个线程都有自己的工作内存(比如CPU的高速缓存)。
线程对变量的所有操作都必须在工作内存中进行,而不能直接对主存进行操作,并且每个线程不能访问其他线程的工作内存。
之所以要用自己的本地内存,主要是利用缓存和改变执行代码顺序达到程序执行效率优化。

总结:主内存工作内存是JMM抽象出来的概念,具体实现要靠别的

当你在Java并发编程中提到“线程的工作内存”时,它指的是:

·逻辑层面:Java内存模型(JMM)定义的一个线程私有的内存区域。

●作用:存储该线程操作所需的主内存中变量的副本。线程的所有变量操作都发生在这里。●核心规则:线程不能直接操作主内存变量,必须通过工作内存副本,并且线程间通信必须经由主内存同步副本状态。

●物理实现:由JVM具体实现决定,通常映射到CPU寄存器、高速缓存等硬件设施,但这属于实现细节,对程序员透明。

目的:定义一套内存可见性规则

(如volatile、 synchronized 、 final 

happens-before等规则解决的正是工作内存与主内存之间的同步问题),保证多线程环境下程序行为的正确性和可预测性。

三级缓存

缓存行

只用了x但是会读一行(64字节)

缓存一致性

这个注解可以让缓存行变化程序不用改变(保证x在单独一行中),但是需要在jvm运行参数加个东西

mesi一致性协议

总结

有序性

 

 半初始化对象

 成员变量赋默认值是半初始化状态

this对象溢出

 可能输出8或者0

 this对象还没初始化完就调用了start

 不要在构造方法里调用start

happensbefore和内存屏障

volatile   
JVM依赖底层实现
JVM定义的内存屏障规则最终需通过操作系统和硬件指令来实现。例如,JVM为 volatile 变量插入的StoreStore屏障,在x86平台可能对应 sfence 指令。
层级分工
抽象层(JVM):定义何时需要内存屏障(如volatile读写、锁释放)。
实现层(OS/硬件):通过CPU指令和缓存协议执行具体的内存屏障操作。

happensbefore是jvm层面
JMM 要求编译器在编译时根据 happens-before 规则插入内存屏障。

happens-before+jmm

 

 

面试题:DCL单例要不要加volatile

对象的创建不是原子性操作,所以有指令重排序的可能。为了禁止指令重排序,所以要引入 volatile。

new一个对象要开辟内存空间,单例模式下如果线程一卡在new的时候,线程二过来了,发现instance有地址(刚申请内存)但是没有数据,所以直接返回,里面内容没有初始化,指令重排导致线程使用这个单例对象,但是它还没有初始化问题

关联就是堆内存地址指向栈中的instance变量

volitile不能保证线程间的指令重排序(原子性)

原子性(atomic,sychornized)

多个线程访问同一个数据产生竞争

无锁保护共享资源

cas

为什么无锁效率高

缺点

 具体过程:synchonized是一个个线程在阻塞的队列中,不消耗资源,而cas类似于等待的过程中原地打转,会消耗系统资源

CAS要配合volatile,一个原子一个可见

不可变类型保证线程安全(日志转换类)

就说返回新对象,而不是改变原来的对象

美团七连问

2.

单例模式下

这个写法有问题,两个线程判断了为空,进入,一个拿锁创建了对象释放锁,另一个线程又拿锁再new一个对象

DCL(双重检查)

此时指令重排导致半初始化,先建立关联再初始化,但是这两个指令中间有线程来判断!=null就拿来用了,此时没初始化,所以为0

3.

指针压缩:两种,一个是类型指针一个是示例数据如果是引用就压缩它

6.

9.

答案:方法区,堆里的是反射用的

用户态与内核态

早期调用sychonized必须从用户态到内核态切换。就是阻塞和唤醒。


 

(!!!基于AQS)CountDownLatch

public class CountDownLatchExample1 {
    // 处理文件的数量
    private static final int threadCount = 6;

    public static void main(String[] args) throws InterruptedException {
        // 创建一个具有固定线程数量的线程池对象(推荐使用构造方法创建)
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        final CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        for (int i = 0; i < threadCount; i++) {
            final int threadnum = i;
            threadPool.execute(() -> {
                try {
                    //处理文件的业务操作
                    //......
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //表示一个文件已经被完成
                    countDownLatch.countDown();
                }

            });
        }
        countDownLatch.await();
        threadPool.shutdown();
        System.out.println("finish");
    }
}

工作流程

  1. 初始化new CountDownLatch(3) 设置 AQS state=3
  2. await():线程尝试获取共享锁,state≠0时阻塞
  3. countDown():每次调用 state=CAS(state-1)
  4. 唤醒时机:当 state==0 时,AQS 唤醒所有等待线程
  5. 不可重置:state 归零后永久开放

ThreadLocal

案例

案例:使用JDBC操作数据库时,会将每一个线程的Connection放入各自的ThreadLocal中,从而保证每个线程都在各自的 Connection 上进行数据库的操作,避免A线程关闭了B线程的连接。

threadlocalmap.set源码讲解

实际使用(解释了同一线程多次set的操作结果)

import java.text.SimpleDateFormat;
import java.util.Random;

public class ThreadLocalExample implements Runnable{

     // SimpleDateFormat 不是线程安全的,所以每个线程都要有自己独立的副本
    private static final ThreadLocal<SimpleDateFormat> formatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd HHmm"));

    public static void main(String[] args) throws InterruptedException {
        ThreadLocalExample obj = new ThreadLocalExample();
        for(int i=0 ; i<10; i++){
            Thread t = new Thread(obj, ""+i);
            Thread.sleep(new Random().nextInt(1000));
            t.start();
        }
    }

    @Override
    public void run() {
        System.out.println("Thread Name= "+Thread.currentThread().getName()+" default Formatter = "+formatter.get().toPattern());
        try {
            Thread.sleep(new Random().nextInt(1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //formatter pattern is changed here by thread, but it won't reflect to other threads
        formatter.set(new SimpleDateFormat());

        System.out.println("Thread Name= "+Thread.currentThread().getName()+" formatter = "+formatter.get().toPattern());
    }

}

对于每一个单独的 ThreadLocal 实例来说,它的实例对象作为键,在每个线程内部的 ThreadLocalMap 中都是唯一的。所以,当不同的线程对同一个 ThreadLocal 实例调用 set() 方法时,它们实际上是在各自线程的 ThreadLocalMap 中设置值,互不影响。也就是说,即使你多次对同一个 ThreadLocal 实例调用 set() 方法,但在不同的线程中,这些调用不会相互干扰,因为每个线程都有自己独立的映射表条目。

然而,在同一个线程内,如果你对同一个 ThreadLocal 实例多次调用 set() 方法,那么确实会覆盖前一次设置的值。这是因为对于给定的线程和 ThreadLocal 实例组合,只会有一个值存在于该线程的 ThreadLocalMap 中。

    强引用

    软引用

    用作缓存

    弱引用

    不用要remove,不然会内存泄漏,value还有引用,因为map还在

    ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,而 value 是强引用。所以,如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。

    这样一来,ThreadLocalMap 中就会出现 key 为 null 的 Entry。假如我们不做任何措施的话,value 永远无法被 GC 回收,这个时候就可能会产生内存泄露。ThreadLocalMap 实现中已经考虑了这种情况,在调用 set()、get()、remove() 方法的时候,会清理掉 key 为 null 的记录。使用完 ThreadLocal方法后最好手动调用remove()方法

    弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。

    弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。

    虚引用

    给写jvm的人用的

    自定义连接池

    线程池篇章

    面试题引入

    两个线程顺序打印A1B2C3..........

    解法一locksupport

    注意第二个线程要先阻塞,不然第二次循环来不一定是第一个线程先执行 出现类似于1AB

    解法二 sync_wait_notify

    notify是唤醒第二个线程;最后加o.notify是因为最后会有线程wait,而另一个线程结束了,没有线程叫醒他了,导致打印成功但是程序退出不了

    保证t2在t1之前打印

    1.自旋锁

    解法三 lock_condition

    解法四升级版condition

    两个condition使我们可以有选择性的唤醒线程和等待线程

    解法五:cas

    线程一把r改成T2之后一直while自旋,占用cpu,此时线程二发现是T2就开始执行

    为什么用volatile:保证线程之间的可见性,改完以后另一个线程立马能发现

    解法六:blockingqueue

    take到了就执行,take不到就阻塞

    三个线程顺序打印ABC

    1.wait-notifyall

    2.renntralock的多条件变量实现

    线程池的好处

    1.减少线程创建和销毁的开销

    2.提高响应速度:任务到达时,无需等待线程的创建即可立即执行,因为线程池中已经有等待的线程

    线程池几个常用类拓展

    execute

    excutorservice

    callable

    线程池运行它会执行call函数,返回值给future。

    futuretask

    实现了runnabletask(runnable和future),所以他可以即执行任务又有返回值

    小总结

    completablefuture

    异步操作任务

    CompletableFuture.supplyAsync(() -> priceOfTM())  // 步骤1:异步执行priceOfTM(),返回Double价格
        .thenApply(String::valueOf)                  // 步骤2:将价格(Double)转换为字符串(String)
        .thenApply(str -> "price " + str)            // 步骤3:拼接字符串,例如 "price 99.9"
        .thenAccept(System.out::println);            // 步骤4:消费结果(打印拼接后的字符串)

    线程池7个参数

    System.in.read代表阻塞

    1.核心线程数 2.最大线程数3.生存时间 4.生存时间的单位

    5.任务队列 6.线程工厂(产生线程的,线程名是什么)7.拒绝策略

    6.默认提供defaultthreadfactory

    默认拒绝策略

    singerthreadexcutor

    为什么要有单线程的线程池

    1.线程池有任务队列,自己new thread任务队列要自己维护

    2.生命周期管理线程池帮你维护

    希望多个任务排队执行。线程数固定为 1,任务数多于 1 时,会放入无界队列排队。任务执行完毕,这唯一的线程也不会被释放。
    区别:
    自己创建一个单线程串行执行任务,如果任务执行失败而终止那么没有任何补救措施,而线程池还会新建一个线程,保证池的正常工作
    Executors.newSingleThreadExecutor() 线程个数始终为1,不能修改
    FinalizableDelegatedExecutorService 应用的是装饰器模式,只对外暴露了 ExecutorService 接口,因此不能调用 ThreadPoolExecutor 中特有的方法
    Executors.newFixedThreadPool(1) 初始时为1,以后还可以修改
    对外暴露的是 ThreadPoolExecutor 对象,可以强转后调用 setCorePoolSize 等方法进行修改
     

    cachedpool

    核心线程数是 0, 最大线程数是 Integer.MAX_VALUE,救急线程的空闲生存时间是 60s,意味着
    全部都是救急线程(60s 后可以回收)
    救急线程可以无限创建
    队列采用了 SynchronousQueue 实现特点是,它没有容量,没有线程来取是放不进去的(一手交钱、一手交
    货)
     

    fixedthreadpool

    核心线程数 == 最大线程数(没有救急线程被创建),因此也无需超时时间
    阻塞队列是无界的,可以放任意数量的任务
    评价 适用于任务量已知,相对耗时的任务

    newFixedThreadPool直接返回一个ThreadPoolExecutor 实例,核
    心线程数和最大线程数都设置为传入的 nThreads,并且使用一个无界
    的 LinkedBlockingQueue 作为工作队列。2newSingleThreadExecutor返的是
    人FinalizableDelegatedExecutorService 实例,但它内部包装的同样是
    人ThreadPoolExecutor ,其核心线程数和最大线程数都设置为1,同样使用无界的LinkedBlockingQueue 。
    因此,在底层实现上,SingleThreadExecutor实际上就是一个线程数量为1的FixedThreadPool它们都基于同一个 ThreadPoolExecutor 类,只是参数不同。

    面试题

    用线程池传数据时用了synchronized 来加锁,保证数据不会被重复推送

    workQueue:线程池等待队列,使用 LinkedBlockingQueue阻塞队列

    工作流程

    线程池七大参数

     拒绝策略

     工作队列

     线程池中线程异常后,销毁还是复用

    直接说结论,需要分两种情况:

    使用execute()提交任务:当任务通过execute()提交到线程池并在执行过程中抛出异常时,如果这个异常没有在任务内被捕获,那么该异常会导致当前线程终止,并且异常会被打印到控制台或日志文件中。线程池会检测到这种线程终止,并创建一个新线程来替换它,从而保持配置的线程数不变。

    使用submit()提交任务:对于通过submit()提交的任务,如果在任务执行中发生异常,这个异常不会直接打印出来。相反,异常会被封装在由submit()返回的Future对象中。当调用Future.get()方法时,可以捕获到一个ExecutionException。在这种情况下,线程不会因为异常而终止,它会继续存在于线程池中,准备执行后续的任务。

    简单来说:使用execute()时,未捕获异常导致线程终止,线程池创建新线程替代;使用submit()时,异常被封装在Future中,线程继续复用。

    这种设计允许submit()提供更灵活的错误处理机制,因为它允许调用者决定如何处理异常,而execute()则适用于那些不需要关注执行结果的场景。

    线程池excute和submit

     线程池如何关闭

    线程池命名

    1.guava

    ThreadFactory threadFactory = new ThreadFactoryBuilder()
                            .setNameFormat(threadNamePrefix + "-%d")
                            .setDaemon(true).build();
    ExecutorService threadPool = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.MINUTES, workQueue, threadFactory)

    2.自己实现threadfactory

    java.util.concurrent.ThreadFactory;
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * 线程工厂,它设置线程名称,有利于我们定位问题。
     */
    public final class NamingThreadFactory implements ThreadFactory {
    
        private final AtomicInteger threadNum = new AtomicInteger();
        private final String name;
    
        /**
         * 创建一个带名字的线程池生产工厂
         */
        public NamingThreadFactory(String name) {
            this.name = name;
        }
    
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r);
            t.setName(name + " [#" + threadNum.incrementAndGet() + "]");
            return t;
        }
    }

    阿里创建线程池规范

    ExecutorService es = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,  new LinkedBlockingQueue003C<Runnable>(10), Executors.defaultThreadFactory(),   new ThreadPoolExecutor.DiscardPolicy());}

    ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 20,   60,       TimeUnit.SECONDS,    new LinkedBlockingQueue<>(200)); 

    ThreadpoolExecutor源码剖析

    为什么要自定义线程池

    threadpoolexecutor应用

    代码构建线程池,并处理有无返回结果的任务

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //1. 构建线程池
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                2,
                5,
                10,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(5),
                new ThreadFactory() {
                    @Override
                    public Thread newThread(Runnable r) {
                        Thread thread = new Thread(r);
                        thread.setName("test-ThreadPoolExecutor");
                        return thread;
                    }
                },
                new MyRejectedExecution()
        );
    
        //2. 让线程池处理任务,没返回结果
        threadPool.execute(() -> {
            System.out.println("没有返回结果的任务");
        });
    
        //3. 让线程池处理有返回结果的任务
        Future<Object> future = threadPool.submit(new Callable<Object>() {
            @Override
            public Object call() throws Exception {
                System.out.println("我有返回结果!");
                return "返回结果";
            }
        });
        Object result = future.get();
        System.out.println(result);
    
        //4. 如果是局部变量的线程池,记得用完要shutdown
        threadPool.shutdown();
    }
    
    
    
    private static class MyRejectedExecution implements RejectedExecutionHandler{
        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            System.out.println("根据自己的业务情况,决定编写的代码!");
        }
    }

    ThreadPoolExecutor的核心属性

    线程池取消传入的任务

     

    Logo

    AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

    更多推荐