首页 > 编程语言 >【JUC并发编程系列】深入理解Java并发机制:线程局部变量的奥秘与最佳实践(五、ThreadLocal原理、对象之间的引用)

【JUC并发编程系列】深入理解Java并发机制:线程局部变量的奥秘与最佳实践(五、ThreadLocal原理、对象之间的引用)

时间:2024-09-25 11:48:27浏览次数:10  
标签:JUC ThreadLocalMap 对象 并发 ThreadLocal 线程 引用 内存

文章目录


【JUC并发编程系列】深入理解Java并发机制:线程局部变量的奥秘与最佳实践(五、ThreadLocal原理、对象之间的引用)

ThreadLocal提供了线程本地变量,它可以保证访问到的变量属于当前线程,每个线程都保存有一个变量副本,每个线程的变量都不同。ThreadLocal相当于提供了一种线程隔离,将变量与线程相绑定,Threadloca适用于在多线程的情况下,可以实现传递数据,实现线程隔离。

java.lang.ThreadLocal 是 Java 中一个用于实现线程局部变量的类。它提供了一种在每个线程中拥有独立变量副本的机制,这样每个线程都可以独立地改变自己的副本,而不会影响其他线程中的副本。

1. 基本 API 介绍

ThreadLocal 类的构造方法

  • ThreadLocal(): 创建一个新的 ThreadLocal 实例。

ThreadLocal 的主要方法

  • T get(): 返回当前线程中该 ThreadLocal 的值。如果当前线程中还没有值,则会调用 initialValue() 方法来初始化这个值。
  • void set(T value): 设置当前线程中该 ThreadLocal 的值。
  • protected T initialValue(): 返回该 ThreadLocal 的初始值。默认返回 null。子类可以重写此方法以提供不同的初始值。

ThreadLocal 的高级特性

  • void remove(): 移除当前线程中该 ThreadLocal 的值。这有助于垃圾回收。

2. 简单用法

下面是一个简单的示例来说明如何使用 ThreadLocal:

public class Example {
    static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 0; // 设置初始值为0
        }
    };

    public static void main(String[] args) {
        // 在主线程中设置值
        threadLocal.set(10);
        System.out.println("Main thread: " + threadLocal.get());

        // 创建新线程并设置不同的值
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                threadLocal.set(20);
                System.out.println("Thread 1: " + threadLocal.get());
            }
        });

        t1.start();
        try {
            t1.join(); // 等待线程结束
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 主线程中的值没有改变
        System.out.println("Main thread after thread 1: " + threadLocal.get());
    }
}

在这个例子中,主线程和线程 t1 都有自己的 ThreadLocal 变量副本,并且它们之间互不影响。

注意事项

  • 使用 ThreadLocal 时要小心内存泄漏的问题。如果不显式调用 remove() 方法或不通过 set(null) 清除引用,那么线程的生命周期内该 ThreadLocal 的值将一直保留,可能导致内存泄漏。
  • 如果 ThreadLocal 不再需要,建议调用 remove() 方法释放资源。

3. 应用场景

1. Spring 事务管理

在 Spring 框架中,TransactionAspectSupport 提供了对事务的支持。其中有一个方法 currentTransactionStatus() 可以返回当前线程中的事务状态,通常用来决定事务是否应该回滚。

TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

**应用场景:**当某个业务逻辑执行失败时,可以通过调用 setRollbackOnly() 方法来标记当前事务需要回滚,而不是提交。这会在事务结束时被事务管理器检测到并触发回滚操作。

**为何使用 ThreadLocal:**因为事务状态是每个线程独有的,所以在不同的线程中处理不同的事务时,需要确保事务状态不被混淆。ThreadLocal 提供了这种隔离性。

2. 获取 HttpServletRequest

在 Web 应用程序中,HttpServletRequest 对象通常在每个 HTTP 请求处理过程中可用。为了在不同的层次中访问这个对象,可以使用 RequestContextHolder

HttpServletRequest request = ((ServletRequestAttributes) 											RequestContextHolder.currentRequestAttributes()).getRequest();

**应用场景:**在过滤器、拦截器或服务层等地方,可能需要访问 HTTP 请求的信息(如请求头、参数等)。

**为何使用 ThreadLocal:**每个 HTTP 请求都是在一个独立的线程中处理的。因此,RequestContextHolder 使用 ThreadLocal 来存储当前线程内的 HttpServletRequest 对象,确保每个线程只能访问与之相关的 HTTP 请求。

3. AOP 调用链传递参数

在分布式系统中,比如使用 LCN 或 Seata 这样的分布式事务框架时,需要在不同的服务间传递全局唯一的事务 ID。

**应用场景:**在发起一个分布式事务时,首先会生成一个全局唯一的事务 ID,然后通过 ThreadLocal 将这个 ID 保存在当前线程中。之后,在后续的调用中,可以从中获取这个 ID 并将其作为参数传递给下游服务。

**为何使用 ThreadLocal:**分布式事务通常涉及多个服务间的调用,每个服务调用都在单独的线程中执行。为了确保每个服务调用能够访问同一个事务 ID,使用 ThreadLocal 来存储这个 ID 是非常合适的。

总结来说,ThreadLocal 在这些场景下被广泛使用是因为它提供了每个线程的数据隔离,这对于保持线程安全和避免并发问题至关重要。特别是在涉及事务管理和跨层通信的情况下,ThreadLocal 的使用可以简化代码并减少潜在的错误。

4. 真实应用场景

例如:在aop中生成全局的id,目标方法获取aop中生成的全局id;

  • aop拦截目标方法,生成全局的id

  • doTest 如何获取在aop类中生成全局id呢

@Component
public class GlobalIDContextholder {

    private ThreadLocal<String> globalIds = new ThreadLocal<String>();

    public String getId() {
        return globalIds.get();
    }

    public void set(String globalId) {
        globalIds.set(globalId);
    }
}

@Aspect
@Component
@Slf4j
public class TestAop {
    @Autowired
    private GlobalIDContextholder globalIDContextholder;

    /**
     * 切入点
     */
    @Pointcut("execution(public * com.mayikt.service.*Service.*(..))")
    public void log() {
    }

    /**
     * 前置操作
     *
     * @param point 切入点
     */
    @Before("log()")
    public void beforeLog(JoinPoint point) {
        UUID globalId = UUID.randomUUID();
        globalIDContextholder.set(globalId.toString());
    }
}

@RestController
public class TsetController {
    @Autowired
    private GlobalIDContextholder globalIDContextholder;

    @GetMapping("/doTest")
    public String doTest() {
        return "从aop中获取的全局id:" + globalIDContextholder.getId();
    }
}

4. Threadlocal与Synchronized区别

ThreadLocalsynchronized 是 Java 中用于处理多线程编程的两种不同机制。它们各自解决的问题和使用场景有所不同。

1.ThreadLocal

ThreadLocal 是一种机制,允许为每个线程维护一个独立的变量副本。这意味着每个线程都可以拥有自己的局部变量,这些变量不会与其他线程共享。

特点:

  • 线程隔离ThreadLocal 为每个线程提供了一个独立的变量副本,因此线程之间不会相互干扰。
  • 安全性:由于每个线程都有自己的变量副本,所以不存在数据竞争问题,不需要担心同步问题。
  • 使用场景ThreadLocal 适用于需要在线程之间隔离数据的情况,例如每个线程需要维护自己的数据库连接、线程局部变量等。

2.synchronized

synchronized 是 Java 中的关键字,用于实现线程之间的同步。它可以用来修饰方法或代码块,确保同一时刻只有一个线程可以执行被 synchronized 修饰的代码。

特点:

  • 互斥性synchronized 确保了同一时刻只有一个线程可以执行被修饰的方法或代码块。
  • 可见性和原子性synchronized 保证了线程之间的可见性和原子性,即一个线程对共享变量的修改对其他线程是可见的,并且操作是不可分割的。
  • 使用场景synchronized 适用于需要多个线程共享数据,并且需要确保数据一致性的情况,例如计数器、共享资源的管理等。

总结

  • ThreadLocal 用于在线程之间隔离数据,每个线程都有独立的变量副本,不需要同步。
  • synchronized 用于保证线程之间的同步,确保共享数据的一致性。

两者的主要区别在于它们解决的问题不同:ThreadLocal 解决的是线程之间的数据隔离问题,而 synchronized 解决的是线程之间的同步问题。在实际应用中,可以根据具体的需求选择合适的方法。

5. 内存溢出和内存泄漏

5.2 内存溢出 (Memory Overflow)

内存溢出是指程序在运行过程中分配的内存超过了JVM所能提供的最大内存限制,导致程序无法正常运行的一种异常情况。在Java中,内存溢出通常发生在以下几个方面:

  1. 堆内存溢出 (Heap Memory Overflow):

    • 当JVM无法为新的对象分配足够的空间时会发生这种情况。这通常是由于不断创建新对象而没有及时释放不再使用的对象,导致垃圾回收机制无法清理足够的内存空间。
    • 常见错误信息为 java.lang.OutOfMemoryError: Java heap space
  2. 栈内存溢出 (Stack Memory Overflow):

    • 发生在递归调用或者深度嵌套调用时,如果递归深度过大或函数调用栈过深,则可能导致栈溢出。
    • 常见错误信息为 java.lang.StackOverflowError
  3. 方法区溢出 (PermGen or Metaspace Overflow):

    • 发生在方法区(在JDK 8之前称为永久代 PermGen space,在JDK 8及以后版本称为元空间 Metaspace)。
    • 这通常是由于加载了过多的类或常量池过大等原因导致的。
    • 常见错误信息为 java.lang.OutOfMemoryError: PermGen spacejava.lang.OutOfMemoryError: Metaspace
  4. 本地方法栈溢出 (Native Method Stack Overflow):

    • 与栈内存溢出类似,但涉及的是本地方法栈。
    • 常见错误信息为 java.lang.OutOfMemoryError: Java native method stack overflow

5.2 内存泄漏 (Memory Leak)

内存泄漏是指程序在申请内存后未能释放已分配的内存,随着时间推移,越来越多的内存被占用但不再被使用,最终可能导致程序崩溃或性能下降。内存泄漏的特点是分配的内存不会被自动回收,即使程序本身还在正常运行。

内存泄漏的表现形式包括但不限于:

  1. 未释放的对象引用:在Java中,如果一个对象不再被任何引用所指向,那么垃圾收集器会自动回收该对象。但如果存在一些无效的引用(例如静态集合中存储的对象引用),那么这些对象将不会被回收,从而导致内存泄漏。

  2. 循环引用:特别是在使用引用计数的垃圾回收机制的语言中,但在Java中,垃圾收集器会处理循环引用,因此这不是一个常见问题。

  3. 缓存管理不当:如果缓存策略不当,可能会导致缓存中存储了大量的不再需要的数据,这些数据占据的内存不会被释放。

  4. 监听器/回调注册:注册了监听器或回调但没有相应的注销操作,会导致监听器或回调对象一直被持有,从而造成内存泄漏。

  5. 日志记录:日志记录可能会保留大量的对象引用,如果没有正确的管理,也可能导致内存泄漏。

  6. 线程局部变量:使用 ThreadLocal 存储对象时,如果没有正确地清理这些对象,也可能会导致内存泄漏。

内存泄漏通常比内存溢出更难以检测和修复,因为它不一定立即导致程序崩溃,但会逐渐消耗内存资源,影响程序性能和稳定性。为了避免内存泄漏,开发人员需要仔细设计程序结构,确保所有分配的资源都能被适时地释放。

6. 强引用、软引用、弱引用和虚引用

在Java中,对象引用分为四种类型:强引用(Strong)、软引用(Soft)、弱引用(Weak)和虚引用(Phantom)。每种引用类型都有其特定的行为和用途,主要用于实现内存管理的不同需求。

6.1 强引用 (Strong Reference)

  • 定义:这是最常用的引用类型。如果一个对象有一个强引用指向它,那么垃圾收集器永远不会回收这个对象。
  • 实现:普通的对象引用就是强引用,例如 Object obj = new Object();
  • 特点:只要强引用存在,对象就不会被回收。

6.2 软引用 (Soft Reference)

  • 定义:软引用是用来描述一些非必需但仍然有用的对象。如果内存足够,软引用指向的对象不会被回收;如果内存不足,那么软引用指向的对象会被回收。
  • 实现:通过 java.lang.ref.SoftReference 类实现。
  • 特点
    • 当 JVM 认为内存不足时,它会回收软引用指向的对象。
    • 一般用于实现内存敏感的缓存。

6.3 弱引用 (Weak Reference)

  • 定义:弱引用比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。(主动调用System.gc();之前)
  • 实现:通过 java.lang.ref.WeakReference 类实现。
  • 特点
    • 在垃圾回收时一定会被回收。
    • 一般用于实现更严格的缓存策略,或者与 ReferenceQueue 结合使用。

6.4 虚引用 (Phantom Reference)

  • 定义:虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它相当于没有任何引用,随时都可能被垃圾回收器回收。
  • 实现:通过 java.lang.ref.PhantomReference 类实现。
  • 特点
    • 虚引用必须与 ReferenceQueue 关联。
    • 当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象之前,把这个虚引用加入到与之关联的队列中。
    • 一般用于跟踪对象的销毁过程,比如实现对象的 “finalize” 通知机制。

6.5 示例代码

下面是一个简单的示例代码,演示如何使用这四种引用类型:

import java.lang.ref.*;

public class ReferenceDemo {
    private static final int _1MB = 1024 * 1024;

    public static void createReferences() {
        // 强引用
        Object strongRef = new byte[4 * _1MB];

        // 软引用
        SoftReference<Object> softRef = new SoftReference<>(new byte[4 * _1MB]);

        // 弱引用
        WeakReference<Object> weakRef = new WeakReference<>(new byte[4 * _1MB]);

        // 虚引用
        PhantomReference<Object> phantomRef = new PhantomReference<>(new byte[4 * _1MB], new ReferenceQueue<>());
    }

    public static void checkReferences() {
        System.out.println("Checking references...");
        
        // 检查软引用
        if (softRef.get() != null) {
            System.out.println("Soft reference is still valid.");
        } else {
            System.out.println("Soft reference has been cleared.");
        }

        // 检查弱引用
        if (weakRef.get() != null) {
            System.out.println("Weak reference is still valid.");
        } else {
            System.out.println("Weak reference has been cleared.");
        }

        // 检查虚引用
        if (phantomRef.get() == null) {
            System.out.println("Phantom reference has been cleared.");
        } else {
            System.out.println("Phantom reference is still valid.");
        }
    }

    public static void main(String[] args) {
        createReferences();

        // 手动触发垃圾回收
        System.gc();
        try {
            Thread.sleep(1000); // 给垃圾收集器一点时间
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        checkReferences();
    }
}
  • 强引用:是最常用的引用类型,指向的对象不会被垃圾回收器回收。
  • 软引用:在内存不足时被回收,适用于实现内存敏感的缓存。
  • 弱引用:在下次垃圾回收时被回收,适用于实现更严格的缓存策略。
  • 虚引用:主要用于跟踪对象的销毁过程,需要与 ReferenceQueue 结合使用。

7. Threadlocal原理分析

  1. 在每个线程中都有自己独立的ThreadLocalMap对象,ThreadLocalMap对象底层基于Entry对象封装。
  2. 如果当前线程对应的ThreadLocalMap对象为空的情况下,则创建该ThreadLocalMap对象,并且赋值键值对。 Key 为当前new ThreadLocal()对象,value 就是为object变量值。

Set方法源码

一个线程对应一个独立ThreadLocalMap,一个ThreadLocalMap k==ThreadLocal value缓存变量的值

//ThreadLocal类
public void set(T value) {
    //获取到当前的线程
    Thread t = Thread.currentThread();
    //获取到当前线程中的threadLocals   threadLocals类型ThreadLocalMap 底层基于Entry对象封装
    //Entry对象 key=ThreadLocal value缓存的变量的值
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        //this就是在调用这个方法之前new 出的 ThreadLocal 对象
        map.set(this, value);
    } else { 
        createMap(t, value);
    }
}
//ThreadLocal类
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

// Thread 类中的
ThreadLocal.ThreadLocalMap threadLocals = null;
//ThreadLocal类中的ThreadLocalMap 类
static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
}

Get方法源码

public T get() {
    //获取当前线程
    Thread t = Thread.currentThread();
    //获取当前线程对应的 ThreadLocalMap 对象
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        //this就是在调用这个方法之前new 出的 ThreadLocal 对象
        //根据具体threadLocal对象(this)获取value值
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

image-20240804175926921

8. ThreadLocal 内存泄漏问题及避免方法

1.ThreadLocal 内存泄漏的原因

ThreadLocal 是 Java 中用于在线程内部存储数据的一种机制。每个线程都有一个独立的 ThreadLocalMap,用来存储线程局部变量。ThreadLocalMap 使用 Entry 对象来封装键值对,其中键是 ThreadLocal 实例,而值则是线程局部变量的实际值。

ThreadLocal 作为Entry对象的key,是弱引用,当ThreadLocal指向null的时候,Entry对象中的key变为null,该对象一直无法被垃圾收集机制回收,一直占用到了系统内存,有可能会发生内存泄漏的问题。

2.如何避免 ThreadLocal 内存泄漏

为了避免 ThreadLocal 导致的内存泄漏,可以采取以下几种措施:

  1. 手动调用 remove() 方法

    每个 ThreadLocal 实例都有一个 remove() 方法,可以用来从当前线程的 ThreadLocalMap 中移除该实例对应的条目。当不再需要某个线程局部变量时,应该显式地调用此方法来释放资源。

    myThreadLocal.remove();
    
  2. 在每次 set() 方法调用时清理 null

    ThreadLocalset() 方法会在设置新值之前检查是否有 null 键存在,并将其从 ThreadLocalMap 中移除。因此,频繁地调用 set() 方法可以帮助清理不再使用的条目。

  3. 使用弱引用

    ThreadLocal 的设计已经考虑到了内存泄漏的风险,它默认使用弱引用作为键,这样即使没有显式地调用 remove() 方法,当没有强引用指向 ThreadLocal 实例时,该实例最终也会被垃圾回收。

3.示例代码

下面是使用 ThreadLocal 并避免内存泄漏的一个示例:

import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;

public class SafeThreadLocal<K> extends ThreadLocal<K> {

    @Override
    protected K initialValue() {
        return null;
    }

    @Override
    public void remove() {
        super.remove();
        // 显式调用 remove 方法以确保从 ThreadLocalMap 中删除该条目
    }

    @Override
    public void set(K value) {
        super.set(value);
        // 每次调用 set 时都会自动清理 null 键
    }

    // 其他方法...

    public static void main(String[] args) {
        SafeThreadLocal<String> threadLocal = new SafeThreadLocal<>();

        // 使用 ThreadLocal
        threadLocal.set("Hello, World!");

        // 不再需要时,显式调用 remove 方法
        threadLocal.remove();
    }
}

// 假设存在一个 ThreadLocalMap 的实现,类似于如下所示
class ThreadLocalMap {
    private final Map<WeakReference<ThreadLocal<?>>, Object> map = new HashMap<>();

    public void put(ThreadLocal<?> key, Object value) {
        map.put(new WeakReference<>(key), value);
    }

    public Object get(ThreadLocal<?> key) {
        return map.get(new WeakReference<>(key));
    }

    public void remove(ThreadLocal<?> key) {
        map.remove(new WeakReference<>(key));
    }

    // 其他方法...
}

通过上述措施,可以有效地避免由 ThreadLocal 引发的内存泄漏问题。

9. ThreadLocal 的引用类型与内存泄漏问题

  • **强引用作为键:**如果 ThreadLocal 的键使用强引用,当我们将 ThreadLocal 的引用设为 null 时,尽管 ThreadLocal 对象本身可能变得不可达,但由于每个线程的 ThreadLocalMap 仍然持有对该对象的强引用,因此 ThreadLocal 对象不会被垃圾回收器回收,这可能会导致内存泄漏。
  • **弱引用作为键:**如果 ThreadLocal 的键使用弱引用,在我们将 ThreadLocal 的引用设为 null 后,如果垃圾回收器运行,那么 ThreadLocal 对象会被回收,其对应的 Entry 的键变为 null。虽然在下一次调用 set 方法时会清理 null 键,但如果在这期间没有调用 set 或者没有机会执行 ThreadLocalMap 的清理逻辑(例如 get 方法中的清理过程),那么含有 null 键的 Entry 将保留在 ThreadLocalMap 中,从而可能导致内存泄漏。
  • **内存泄漏的根本原因:**不论使用强引用还是弱引用,如果不适当地管理 ThreadLocal 的生命周期,都可能发生内存泄漏。最根本的原因在于 ThreadLocalMap 的生命周期通常与线程相同,如果线程持续运行且不清理过期的 ThreadLocal 实例,即使使用了弱引用,也可能导致内存泄漏。
  • **避免内存泄漏的方法:**为了防止内存泄漏,建议在不再需要某个 ThreadLocal 实例时显式调用其 remove 方法,或者确保在适当的时机让垃圾回收器能够回收不再使用的 ThreadLocal 实例。此外,了解 ThreadLocalMap 的清理机制并适时触发清理也是很重要的。

标签:JUC,ThreadLocalMap,对象,并发,ThreadLocal,线程,引用,内存
From: https://blog.csdn.net/weixin_68020300/article/details/142446114

相关文章

  • 记一次并发导致的事故
    记一次并发导致的事故简化业务逻辑如下:创建一批收件人;点击发送按钮时发送邮件给这批发件人;现象是有一批人收到了两封邮件,还有少部分人收到了3封.原因分析每次触发发送任务都有一个唯一的traceid,记录日志时会带上traceid,将日志按traceid分组获取第一条和最......
  • # 高可用的并发解决方案nginx+keepalived(三)
    高可用的并发解决方案nginx+keepalived(三)一、Nginx搭建图片服务器针对任何站点,几乎都要访问图片,而一个网页里面几乎有好些张图片,这时候会占据大量tomcat连接,造成大量并发,我们可以通过Nginx配置直接访问硬盘里的图片,绕开tomcat。1、在CentOS7服务器上,创建/usr/local/im......
  • 并发处理的利器:深入探讨锁分离设计+6大分离场景(高并发篇)
    锁分离设计的本质在于将对共享资源的访问操作根据其类型或性质区分开来,并为每种操作提供独立的锁。这种设计背景通常源于对高并发系统的需求,其中多个线程或进程需要频繁地对共享资源进行读写或其他操作。在传统的锁机制中,所有操作都可能使用同一把锁,这在高并发环境下会导致严重的......
  • 《深入解析 Java 中的 ThreadLocal》
    ThreadLocal1.概述ThreadLocal被称为线程局部变量,用于在线程中保存数据。由于在ThreadLocal中保存的数据仅属于当前线程,所以该变量对其他线程而言是隔离的,也就是说该变量是当前线程独有的变量。ThreadLocal用于在同一个线程间,在不同的类和方法之间共享数据的的场景,也可以......
  • 【Java】并发编程的艺术:悲观锁、乐观锁与死锁管理
    目录一、乐观锁和悲观锁二、ReadWriteLock三、StampedLock四、Semaphore五、死锁的条件六、如何发现死锁七、如何避免死锁一、乐观锁和悲观锁        悲观锁(PessimisticLocking)具有强烈的独占和排他特性。它指的是对数据被外界修改持保守态度。因此,在整......
  • 并发编程工具集——Fork/Join-上(三十六)
    简述前面提到的线程池、Future、CompletableFuture和CompletionService,这些工具类都是在帮助我们站在任务的视角来解决并发问题,而不是让我们纠缠在线程之间如何协作的细节上(比如线程之间如何实现等待、通知等)。精髓:对于简单的并行任务,你可以通过“线程池+Future”的方案......
  • 一文夯实并发编程的理论基础
    JMM内存模型定义java内存模型(即javaMemoryModel,简称JMM),不存在的东西,是一个概念,约定主要分成两部分来看,一部分叫做主内存,另一部分叫做工作内存。java当中的共享变量;都放在主内存当中,如类的成员变量(实例变量),还有静态的成员变量(类变量),都是存储在主内存中的。每一个线程都可以......
  • 一文夯实并发编程的理论基础
    JMM内存模型定义java内存模型(即javaMemoryModel,简称JMM),不存在的东西,是一个概念,约定主要分成两部分来看,一部分叫做主内存,另一部分叫做工作内存。java当中的共享变量;都放在主内存当中,如类的成员变量(实例变量),还有静态的成员变量(类变量),都是存储在主内存中的。每一个线程都可......
  • 线程同步:锁,条件变量,并发
    1)锁mutex2)条件变量头文件<condiction_variable>condition_variablecv;cv.wait(_lock,谓语)//使用谓语检查是否满足唤醒条件,防止假唤醒usingnamespacestd; mutex_mutex; condition_variablecv; //condition_variablecv2; intnum=1; threadth1([&](){ int......
  • locust+python高并发压测总结
    locust+python全链路压测总结1.什么是接口?为系统提供数据服务的叫做接口(Interface)locust安装pipinstalllocust安装完成后:locust-h查看是否安装成功2.如何编写locust脚本?编写locust脚本主要分为以下几个步骤:导入Locust类定义用户行为定义任务运行脚本2.1导入Lo......