并发编程常见设计方案 不可变模式


并发问题的产生的原因最核心的原因就是 共享 + 读写,如果一个变量被共享,而且还有多个线程对其进行读写,那么就一定会发生并发问题。我们解决问题的思路有两种,一种是根据问题的特点制定相关的解决方案,另外一种是不让问题产生。并发问题的出现一定是,有些线程对共享变量进行读操作,有些线程对同一共享变量进行写操作。就像解决死锁问题一样,破坏死锁的条件就能解决死锁问题,那么如果我们破坏产生并发问题的条件,那么也可以避免并发产生的问题,而今天要介绍的并发设计方案:不可变模式 破坏的就是读写条件,如果多个线程只读的话,就不会有并发问题,简单来说,就是对象一旦被创建之后,就不可被修改,始终保持着初始状态。对应到Java代码中,就是类+final, 类中的属性+final 并只提供可读方法, 在Java中有很多类都是这样的设计,比如基本数据类型的包装类(Long,Integer)还有肯定会使用的String类,这些类的设计都遵守不可变模式,

public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];

上面是Java8的代码,可以看到String类的设计符合不可变模式,但是不可变模式不是不能有写操作的方法吗?String类可是有replace()的呀,其实不可变模式的核心只要保证对象的状态从创建开始一直到程序结束都不改变就可以了。如果你看过 replace()的源码,就知道他其实返回的是一个新的String对象

public String replace(char oldChar, char newChar) {
if (oldChar != newChar) {
int len = value.length;
int i = -1;
char[] val = value; /* avoid getfield opcode */
​
while (++i < len) {
if (val[i] == oldChar) {
break;
}
}
if (i < len) {
char buf[] = new char[len];
//这里就发生了复制
for (int j = 0; j < i; j++) {
buf[j] = val[j];
}
while (i < len) {
char c = val[i];
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
return new String(buf, true);
}
}
return this;
}

但是不可变模式也是有缺点的,那就是会大量创建对象,导致内存的浪费,于是我们可以通过享元模式,将相同的对象缓存起来,当要继续创建相同的对象时,就不在创建,而是从缓存池中取出来就好了,Java中的基本数据类型包装类就是这样设计的,比如Long类型就会提前缓存[-128,127]的数。

public static Long valueOf(long l) {
final int offset = 128;
if (l >= -128 && l <= 127) { // will cache
return LongCache.cache[(int)l + offset];
}
return new Long(l);
}

String也会做缓存,只是缓存的都是String的字面量,即写死在代码里的字符串值,比如 String s = “pandaer” “pandaer”就是字面量,他就会被缓存到字符串常量池中,如果后面在代码中还出现“pandaer”的时候用的就是字符串常量池中的同一个对象。


0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
pandaer
作者分享
#AI应用开发 springAI是如何实现mcp的工具调用的,在springai中,工具调用被抽象成了一个toolcallback,无论是我们自己写的工具调用,还是mcp提供的,最终都会变成这个类的实例,然后再大模型返回的结果里,如果有工具调用,就会调用toolcallback的call方法,来执行真正的工具调用
4
#AI应用开发 今天学习了如何利用springAI来开发mcp server以及mcp client。开发mcp client很简单,只需要引入依赖,配置一下就好。开发一个mcp server也是一样 先要引入一个依赖,然后手动注入toolcallbackprovider这个bean就行了。需要利用tool注解来自定义需要对外提供的工具
4
#AI应用开发 今天学习了toolcall,本质还是给大模型提供上下文信息,让其生成调用。在传递给大模型的消息中有一个额外的字段叫做tools,里面定义了工具的声明。在模型的响应中有一个字段叫做tool_calls里面定义了要调用哪些工具。 工具的定义要清晰明了,参数结构尽可能的简单,不要试图在一次请求中拼装复杂的工具链。满足需求的情况下,越简单,越清晰越好
4
#AI应用开发 springAI实现短期记忆(会话内记忆)的实现是基于chatmemory来实现的,chatmemory这个接口专注于我要把那些消息交给大模型,而chatmemoryrepostory 这个接口专注的是会话中产生的消息怎么存? 存哪里? 是数据库还是内存?
4
#AI应用开发 springAI中 目前学习了两套api,一个是chatmodel,一个是chatclient。 chatmodel是封装了大模型提供商的chat请求,让我们可以很方便的调用模型,而不需要手写http请求。他提供了两个方法 一个是call,一个是stream 对应chat请求的两套模式,一个是非流式输出,一个是流式输出。所以这个更像是对大模型的抽象。 chatclient是基于chatmodel的封装,提供了更多的api,方便我们快速搭建相关的功能。 springAI 提供了prompt来抽象给chat的请求内容,不仅仅包含消息列表,还有一些模型配置,但是我感觉prompt这个名字不好,我宁愿叫chatrequest,也不叫prompt,因为一段小文本就是prompt
3
下载 APP