到底什么时候该用多线程?

我想大多数人在学习多线程时都会对此问题有所顾虑,尽管多线程的概念不难理解,那我们什么时候该用它呢?在大多数情况下,我们写了程序,发现有时必须使用多线程才能得到理想的运行结果,于是我们按照资料调用相关的线程类库或API改善程序,并使其正常运行;但是,到底存不存在一种判断依据,能够明确的指导我们正确地使用多线程机制来解决问题呢?笔者对此进行了一番思考,在此说说我的想法以供参考。

在开始之前,先引入几个问题,这些问题最终都会在这篇文章里找到答案。

问题情景[0]:设计一个简单的UI:包括一个文本标签和一个按钮,在点击按钮时文本显示由0~10的增长,每秒增长量为1。

问题情景[1]:某同学编写的坦克大战程序中,每一个坦克和子弹均使用一个独立的线程,这是否合理?(当然不合理。。。)如果是你,你会怎么编写这个程序?

笔者认为,多线程的使用离不开“阻塞”这个概念,不过,我想先对这个概念加以扩充,首先先来回想一下阻塞概念原本的意思,简单的说,就是程序运行到某些函数或过程后等待某些事件发生而暂时停止CPU占用的情况;也就是说,是一种CPU闲等状态,不过有时我们使用多线程并不一定是保持闲等时的程序响应,例如在追求高性能的程序中,某条线程在进行高强度的运算,此时若对运算性能不满意,我们也许会再启动若干条运算线程(当然,是在CPU有运算余力的情况下),此时,高强度运算应该归为一种“忙等”状态。

说到这,多线程归根究底是为了解决"等"的问题,那我们这样定义一个阻塞过程:程序运行该过程所消耗的时间有可能在运行上下文间产生明显的卡顿;这里使用“可能”是因为有些情况下,诸如Socket通信,如果数据源源不断的进入,那么阻塞的时间可能非常小,但我们还是使用了一条线程(nio另说)来处理它,因为我们无法保证数据到来的持续性和有效性;"卡顿"带有主观臆想,也就是说是使用者(人或一些自动化程序)不可接受的。

接下来,对什么时候使用多线程做一个回答:编写程序过程中需要使用某些阻塞过程时,我们才使用多线程,或者更进一步讲,使用多线程的目的是对阻塞过程中的实际阻塞的抽象提取。前半句话应该很好理解,而后面的一句虽然不太好懂,不过它对一个程序应具有的合理线程数量进行了阐释(这点接下来解释)。

好了,接下来我们回顾一些两个问题,并对它们做出解答:

问题情景[0]:

为了方便表达,笔者在此采用伪Java代码来阐释解答过程。首先我们有一个Label textShower用于显示文本,Button textChanger作为点击的按钮

这个问题是笔者还是一名小菜时遇到的,当时笔者是这么写的:

public class MyFrame
{Label textShower;Button textChanger;public MyFrame//实例化等省略{textChanger.setOnClickListener(new OnClickListener(){public void onClick(MouseEvent e){for(int i = 1;i <= 10;i++){textShower.setText(i+"");//设置文字Thread.sleep(1000);//等待一秒}}});}
}

程序的执行结果是点击后10秒没有响应,然后数值被设定为10;现在知道了,是由于AWT消息线程同时负责着图像的绘制刷新操作,而Thread.sleep属于之前的阻塞过程,导致画面停止响应。

当时老师是这么教给我的:

public class MyFrame
{Label textShower;Button textChanger;public MyFrame//实例化等省略{textChanger.setOnClickListener(new OnClickListener(){public void onClick(MouseEvent e){new Thread(){public void run(){for(int i = 0;i < 10;i++){textShower.setText(i+"");//设置文字Thread.sleep(1000);//等待一秒}    }}.start();}});}
}

当然,这样确实能够满足题目要求,我也因此开心了一阵,不过不久我就有了新的问题:每按一次按钮产生一个线程是否合理呢?如果这样的文本组合再多几个,我也要创建更多的线程吗?要是使用者是个熊孩子来这里一通狂按这程序还受得了么....

后来,在面向对象思想深入人心后,稍微懂面向对象的人都会知道使用抽象来简化程序,只不过在上面的问题中,我们需要抽象的不是具体的实体,而是“实际阻塞”这种抽象概念。

在上面的代码中,笔者写的第一个onClick函数属于一个阻塞过程,其中sleep属于“实际阻塞”。

而改版的代码中,只是使用多线程将原本的阻塞过程变为了非阻塞过程,实际上是使用了一个独立的线程将整个阻塞过程包含在内,并没有做任何的抽象。

问题情景[1]:在这个问题中,将主要讨论实际阻塞的抽象和合理线程数量的问题。

这个情景是不久前一位网友问我的,他的毕业设计是编写一个坦克大战的游戏,在编的差不多的时候,突然想到每一辆坦克、每一发子弹都是用单独的线程不是很合理,问我如何改进。用这个例子说明实际阻塞的抽象再合适不过了,我们先看看他写的代码片段:

坦克类:

public class Tank extends Thread{float x;//这里以横向移动为例子,只写一个属性float speed = 1f;public void run(){drawtank();//清除上一次的绘制,根据横坐标x画一个坦克x+=speed;Thread.sleep(17);//约合一秒60次}
}

子弹类:

public class Bullet extends Thread{float x;//这里以横向移动为例子,只写一个属性float speed = 10f;public void run(){drawbullet();//清除上一次的绘制,根据横坐标x画一个子弹x+=speed;Thread.sleep(17);//约合一秒60次}
}

其实这样异步的绘制会使画面产生明显的抖动,而且用于同步的逻辑也十分复杂,并不是一个好的方案。
其实上面两个类中的run方法中,只有sleep属于实际阻塞,也就是说是可以被抽象出来的,我们只要一个线程,每过17毫秒执行一些列非阻塞过程即可。

上述过程中,绘制及坐标的运算属于非阻塞过程,我们将其抽象为一个接口:

public interface Drawable
{public void draw();
}

之后我们书写抽象实际阻塞的线程类:

public class BlockThread extends Thread
{Collection<Drawable> c = new Collection<Drawable>();public void run(){for(Drawable d:c){d.draw();}Thread.sleep(17);}//封装对成员c的同步CRUD不赘述public void addDrawable(Drawable d);public void removeDrawable(Drawable d);...
}

最后,坦克和子弹的改动:
坦克类:

public class Tank implements Drawable{float x;//这里以横向移动为例子,只写一个属性float speed = 1f;@Overridepublic void draw(){drawtank();//清除上一次的绘制,根据横坐标x画一个坦克x+=speed;}
}

子弹类:

public class Bullet implements Drawable{float x;//这里以横向移动为例子,只写一个属性float speed = 10f;@Overridepublic void draw(){drawbullet();//清除上一次的绘制,根据横坐标x画一个子弹x+=speed;}
}

我们可以发现:原有的实际阻塞过程已经被抽象到一个线程之中,而非阻塞过程,诸如绘制和坐标运算依然作为方法保留到对应类中,这样,无论有多少坦克和炮弹,只要非阻塞过程的运算压总和力不至于逼近阻塞的程度,使用一个线程即可完成所有工作。
    而且,如果想要添加游戏元素,例如其他类型的子弹,只需要实现Drawable接口即可。

写到这,UI的问题也就解决了,诸如sleep这样纯粹延时的阻塞非常容易抽象,我们可以如法炮制,使用一个线程解决所有的数值延时自增的问题。但并不是所有实际阻塞都易于抽象,如socket.read(byte[] b);这样的方法显然没有抽象的余地,因此才引出后来的nio方案。

最后,我们对什么时候使用多线程,以及使用线程的数量做一个总结:在编写程序时,遇到了阻塞过程而不想使整个程序停止响应时,应使用多线程;一个程序的合理线程数量取决于对实际阻塞的抽象程度。
--------------------- 
作者:三向板砖 
来源:CSDN 
原文:https://blog.csdn.net/shuzhe66/article/details/25484195 
版权声明:本文为博主原创文章,转载请附上博文链接!

什么情况下使用多线程相关推荐

  1. c#中跨线程调用windows窗体控件 .我们在做winform应用的时候,大部分情况下都会碰到使用多线程控制界面上控件信息的问题。然而我们并不能用传统方法来做这个问题,下面我将详细的介绍。...

    首先来看传统方法: public partial class Form1 : Form{public Form1() { InitializeComponent(); } private void F ...

  2. 多线程在任何情况下均能提高效率吗?

    早段时间在网上看到一篇文章,其中就写了使用多线程模型实现文件的快速搜索.由此使我一直在考虑,多线程模型真的能够提高应用程序的效率吗?如果不能,那么多线程模型能干什么呢? 很多程序员一谈到提高应用程序效 ...

  3. java socket 回调函数_请问Java网络编程如何在不使用多线程的情况下实现异步返回?...

    我指的是在不使用多线程的情况下进行并发处理 具体的情况是,在不使用多线程的情况下,服务器侦听某个端口,在有连接进来的时候会调用某个函数对此连接进行处理,但是由于处理的过程可能会比较长,为了不让后面连接 ...

  4. Spring Cloud Feign传输Header,并保证多线程情况下也适用

    Spring Cloud Feign传输Header,并保证多线程情况下也适用 一.现象 微服务在生产中,常遇到需要把 header 传递到下一子服务的情况(如服务A访问服务B的接口,需要传递head ...

  5. 协程为什么比多线程快?在什么情况下更快?

    协程在什么情况下更快? 据我所知,是在多线程IO时. 协程是单线程异步,但是为什么在多线程IO时协程更快? IO的操作对象(一个文件.一个变量.一块内存空间)可能只能处理一个线程的需求,但是有多个线程 ...

  6. Linux下的多线程编程

    1 引言 线程(thread)技术早在60年代就被提出,但真正应用多线程到操作系统中去,是在80年代中期,solaris是这方面的佼佼者.传统的Unix也支持线程的概念,但是在一个进程(process ...

  7. java超线程_超线程多核心下Java多线程编程技术分析

    在学习编程的过程中,我觉得不止要获得课本的知识,更多的是通过学习技术知识提高解决问题的能力,这样我们才能走在最前方,本文主要讲述超线程多核心下Java多线程编程技术分析,更多Java专业知识,广州疯狂 ...

  8. linux 下的 多线程http 下载器

    Linux下多线程下载工具-Axel Axel是命令行下的多线程下载工具,支持断点续传,速度通常情况下是Wget的几倍. 它的官方网站: http://wilmer.gaa.st/main.php/a ...

  9. 如何处理高并发写入mysql_如何处理高并发情况下的DB插入

    转载以便以后学习使用,谢谢! 插入数据库,在大家开发过程中是很经常的事情,假设我们有这么一个需求: 1.  我们需要接收一个外部的订单,而这个订单号是不允许重复的 2.  数据库对外部订单号没有做唯一 ...

  10. 如何处理高并发情况下的DB插入

    转载以便以后学习使用,谢谢! 插入数据库,在大家开发过程中是很经常的事情,假设我们有这么一个需求: 1.  我们需要接收一个外部的订单,而这个订单号是不允许重复的 2.  数据库对外部订单号没有做唯一 ...

最新文章

  1. Android属性动画源代码解析(超详细)
  2. 利用python提取网站曲线图数据
  3. java异常处理 ppt_Java异常处理、多线程ppt课件
  4. VS2015如何使自己的exe文件在别人的电脑上运行(找不到MSVCP140D.dll)
  5. 初步认识泊松重建(比较全的综合教程)
  6. python教程视频下载-python怎么下载视频
  7. 游戏筑基开发之C语言编程技巧
  8. 头条号个人中心登录_注册登录系统
  9. 答题卡html怎么实现,求一段js代码,实现网上答题,有很多道选择题,每选择一题,在跟随页面的答题卡上有相应的标记...
  10. 天空的颜色 363
  11. 【English】【托业】【四六级】写译高频词汇
  12. ChatGPT有多厉害,影响到谷歌地位?
  13. Win10、Win11跳过联网注册微软账户激活方法
  14. secret学习笔记
  15. qq邮箱993服务器地址,ios邮箱绑定qq邮箱提示993服务器连接超时
  16. 机器学习——K-Means聚类算法及其应用
  17. python可视化丨从1896年说起,用数据看 120 年奥运变迁
  18. SAP FI-CO应收应付重分类
  19. iBATIS、Hibernate和JPA:哪一款最适合你
  20. python列表推导式是什么

热门文章

  1. json dumps dump区别
  2. 理解numpy dot函数
  3. Majority Number III
  4. Magento报错之SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry for key 1
  5. 免费的分区软件MiniTool Partition Wizard Free
  6. OpenCV-3.4.3图像通道处理
  7. JMS学习十一(ActiveMQ Consumer高级特性之独有消费者(Exclusive Consumer))
  8. 《Entity Framework 6 Recipes》中文翻译系列 (15) -----第三章 查询之与列表值比较和过滤关联实体...
  9. Android NDK 如何缩减库的大小
  10. BS CS 优缺点比较 及 适应场合 (部分转载+个人见解)