换个角度看Java的Thread

在Java的世界中,新开一个线程很容易,你只需要用下面的代码。

1
2
Thread thread = new Thread(() -> {while(true) {doSomeThing...}});
thread.run();

在深入一点,你应该知道Thread.run()调用了Linux的fork()函数,从父线程中copy出一个一摸一样的子线程,然后开出了一个新的线程。

但是呢?在深入思考一下,提一个问题:

为啥说Go可以随意Goroutine几百几万个?而Java确不建议开那么多的线程,都是建议使用线程池来处理问题?或者说代码里面随意的使用new Thread()可以吗?

答案就是: Go提供的线程是协程是语言层面的,线程切换不必牵涉CPU上下文切换(而且还提供了Forkjoin机制,当有单个协程阻塞,可以分配),Java的线程使用的是Linux的fork(),会涉及CPU上下文切换。

关于协程

关于Go的协程,更加细致的介绍可以看这两篇文章:
https://blog.youkuaiyun.com/truexf/article/details/50510073
https://www.jianshu.com/p/533d58970397

可以看到,由于Java的线程切换,涉及从用户态切换到内核态,多了一步操作。如果Java的线程过多就会造成CPU频繁上下文切换,浪费额外时间。所以,开发协程API对于语言来说还是有不小的价值。(是不是觉得Java有点弱,协程都不支持。)

因此在Java世界还是需要合理的使用线程,无论是GC线程还是业务线程。比较经典的例子就是,web服务器的线程池配置。当我们知道Java开了过多的线程之后反而会减低性能,那么我们的web服务器应该如何调优线程池配置?

以NIO模型为例,分为Accept、Selector、还有业务线程,这里的线程池该如何分配呢?

NIO配置: Jetty

全局使用一个线程池QueuedThreadPool,而最小线程数8最大200,Acceptor线程默认1个,Selector线程数默认2个

NIO配置: undertow

undertow 配置的是 Acceptor 递归也就是线程数 1,IO worker是CPU核数,而工作线程数是CPU * 8;

参考:
https://www.cnblogs.com/maybo/p/7784687.html
https://blog.youkuaiyun.com/rickiyeat/article/details/78906366

NIO配置: 自己使用Netty来实现

public class NettyHttpController {

    public void run() throws InterruptedException {

        // accept 线程池,默认为1
        EventLoopGroup boss = new NioEventLoopGroup();
        // selector 线程池,
        EventLoopGroup worker = new NioEventLoopGroup(Integer CPU_Num);

        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(boss, worker);
        bootstrap.channel(NioServerSocketChannel.class);

        bootstrap.childHandler(new ChannelInitializer<Channel>() {
            @Override
            protected void initChannel(Channel channel) throws Exception {
                ChannelPipeline channelPipeline = channel.pipeline();
                
                // 业务线程池,伪代码,这里需要开发考虑
                channel.add(new Executor(() -> {
                    doService();
                    ....
                    channel.respnse("调用成功");
                }));
            }
        });

        ChannelFuture channelFuture = bootstrap.bind(9999).sync();
    }

    public static void main(String[] args) throws InterruptedException {
        new NettyHttpController().run();
    }

}

线程模型

如果不考虑网络IO等因素,只考虑多线程业务系统的情况,又该如何处理线程池呢?是需要模仿NIO对线程进行分块处理还是怎么样呢?

在代码世界中,如果语言能力比不上人家,那么构建一个好的模型,同样能够击败对手。在《七周七并发》这本书中就推荐了7种并发模型。比较类似的,就是CSP模型(管道模型)和Actor模型,使用Go和Java分别能很好的实现这两者。

CSP模型

  1. 接收int 数组,并返回一个channel

    func gen(nums ...int) <-chan int {
        out := make(chan int)
        go func() {
            for _, n := range nums {
                out <- n
            }
            close(out)
        }()
        return out
    }

     

  2. 从channel中接收数据,并进行2次方,并放到一个新的channel

    func sq(in <-chan int) <-chan int {
        out := make(chan int)
        go func() {
            for n := range in {
                out <- n * n
            }
            close(out)
        }()
        return out
    }

     

  3. 来看例子一: 如何使用刚刚的两个channel

  4. func main() {
        c := gen(2, 3)      // 函数一
        out := sq(c)        // 函数二
    
        // 消费输出结果
        fmt.Println(<-out) // 4
        fmt.Println(<-out) // 9
    }

     

  5. 例子二: channel和Actor不同的是,channel关注channel,而不是接收消息的主体。因此,你还可以将一个channel发送给多个函数消费。

    func main() {
        in := gen(2, 3)
    
        // 启动两个 sq 实例,即两个goroutines处理 channel "in" 的数据
        c1 := sq(in)
        c2 := sq(in)
    
        // merge 函数将 channel c1 和 c2 合并到一起,这段代码会消费 merge 的结果
        for n := range merge(c1, c2) {
            fmt.Println(n) // 打印 4 9, 或 9 4
        }
    }

     

Actor模型

不同于channel,Actor模型更加类似人类世界的交互模式。任何的交互都是异步的,都需要容忍失败。

public class Hello1 {

    public static void main(String[] args) {
        ActorSystem system = ActorSystem.create("actor-demo-java");
        ActorRef hello = system.actorOf(Props.create(Hello.class));

        // 需要定义接收者
        hello.tell("Bob", ActorRef.noSender());

        // 有一个线程单独处理,都是异步的,需要容忍失败
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) { /* ignore */ }
        system.shutdown();
    }
 
    private static class Hello extends UntypedActor {
    	
        public void onReceive(Object message) throws Exception {
            if (message instanceof String) {
                System.out.println("Hello " + message);
            }
        }
    }
}

对于我而言,Actor更类似于将消息系统引入到了单机业务中来,他更面向于各种复杂的情况。更类似一个完整的业务情况。

总结

本文从Java线程的实现,到NIO模式线程池配置,再到并发模型,来讲解了如何使用多线程。可以看到一个同步线程是最最简单,但是对于CPU而言,浪费类很多调度时间。如果对业务进行抽象,合理进行建模。无论是针对业务,还是针对系统性能,都能有很大的提升。

转自:https://runningegg.cn/2018/12/22/%E6%8D%A2%E4%B8%AA%E8%A7%92%E5%BA%A6%E7%9C%8BJava%E7%9A%84Thread/#more

1. 用户与身体信息管理模块 用户信息管理: 注册登录:支持手机号 / 邮箱注册,密码加密存储,提供第三方快捷登录(模拟) 个人资料:记录基本信息(姓名、年龄、性别、身高、体重、职业) 健康目标:用户设置目标(如 “减重 5kg”“增肌”“维持健康”)及期望周期 身体状态跟踪: 体重记录:定期录入体重数据,生成体重变化曲线(折线图) 身体指标:记录 BMI(自动计算)、体脂率(可选)、基础代谢率(根据身高体重估算) 健康状况:用户可填写特殊情况(如糖尿病、过敏食物、素食偏好),系统据此调整推荐 2. 膳食记录与食物数据库模块 食物数据库: 基础信息:包含常见食物(如米饭、鸡蛋、牛肉)的名称、类别(主食 / 肉类 / 蔬菜等)、每份重量 营养成分:记录每 100g 食物的热量(kcal)、蛋白质、脂肪、碳水化合物、维生素、矿物质含量 数据库维护:管理员可添加新食物、更新营养数据,支持按名称 / 类别检索 膳食记录功能: 快速记录:用户选择食物、输入食用量(克 / 份),系统自动计算摄入的营养成分 餐次分类:按早餐 / 午餐 / 晚餐 / 加餐分类记录,支持上传餐食照片(可选) 批量操作:提供常见套餐模板(如 “三明治 + 牛奶”),一键添加到记录 历史记录:按日期查看过往膳食记录,支持编辑 / 删除错误记录 3. 营养分析模块 每日营养摄入分析: 核心指标计算:统计当日摄入的总热量、蛋白质 / 脂肪 / 碳水化合物占比(按每日推荐量对比) 微量营养素分析:检查维生素(如维生素 C、钙、铁)的摄入是否达标 平衡评估:生成 “营养平衡度” 评分(0-100 分),指出摄入过剩或不足的营养素 趋势分析: 周 / 月营养趋势:用折线图展示近 7 天 / 30 天的热量、三大营养素摄入变化 对比分析:将实际摄入与推荐量对比(如 “蛋白质摄入仅达到推荐量的 70%”) 目标达成率:针对健
1. 用户管理模块 用户注册与认证: 注册:用户填写身份信息(姓名、身份证号、手机号)、设置登录密码(需符合复杂度要求),系统生成唯一客户号 登录:支持账号(客户号 / 手机号)+ 密码登录,提供验证码登录、忘记密码(通过手机验证码重置)功能 身份验证:注册后需完成实名认证(模拟上传身份证照片,系统标记认证状态) 个人信息管理: 基本信息:查看 / 修改联系地址、紧急联系人、邮箱等非核心信息(身份证号等关键信息不可修改) 安全设置:修改登录密码、设置交易密码(用于转账等敏感操作)、开启 / 关闭登录提醒 权限控制:普通用户仅能操作本人账户;管理员可管理用户信息、查看系统统计数据 2. 账户与资金管理模块 账户管理: 账户创建:用户可开通储蓄卡账户(默认 1 个主账户,支持最多 3 个子账户,如 “日常消费账户”“储蓄账户”) 账户查询:查看各账户余额、开户日期、状态(正常 / 冻结)、交易限额 账户操作:挂失 / 解挂账户、申请注销账户(需余额为 0) 资金操作: 转账汇款:支持同行转账(输入对方账户号 / 手机号),需验证交易密码,可添加常用收款人 存款 / 取款:模拟存款(输入金额增加余额)、取款(输入金额减少余额,需不超过可用余额) 交易记录:按时间、类型(转入 / 转出 / 存款 / 取款)查询明细,显示交易时间、金额、对方账户(脱敏显示)、交易状态 3. 账单与支付模块 账单管理: 月度账单:自动生成每月收支明细,统计总收入、总支出、余额变动 账单查询:按月份、交易类型筛选账单,支持导出为 Excel 格式 还款提醒:若有贷款(简化版可模拟),系统在还款日 3 天前发送提醒 快捷支付: 绑定支付方式:添加银行卡(系统内账户)作为支付渠道 模拟消费:支持输入商户名称和金额,完成支付(从账户余额扣减) 支付记录:保存所有消费记录,包含商户、时间、金额、支付状态 4.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值