精华内容
下载资源
问答
  • Java:多线程:到底什么时候用多线程

    万次阅读 多人点赞 2018-09-30 16:27:29
    系统接受实现多用户多请求的高并发时,通过多线程来实现。   二、线程后台处理大任务 一个程序是线性执行的。如果程序执行到要花大量时间处理的任务时,那主程序就得等待其执行完才能继续执行下面的...

    一、高并发

    系统接受实现多用户多请求的高并发时,通过多线程来实现。

     

    二、线程后台处理大任务

    一个程序是线性执行的。如果程序执行到要花大量时间处理的任务时,那主程序就得等待其执行完才能继续执行下面的。那用户就不得不等待它执行完。

    这时候可以开线程把花大量时间处理的任务放在线程处理,这样线程在后台处理时,主程序也可以继续执行下去,用户就不需要等待。线程执行完后执行回调函数。

     

    三、大任务

    大任务处理起来比较耗时,这时候可以起到多个线程并行加快处理(例如:分片上传)。

     

    连续的操作,需要花费忍无可忍的过长时间才可能完成
    并行计算
    为了等待网络、文件系统、用户或其他I/O响应而耗费大量的执行时间
    所以说,在动手之前,先保证自己的应用程序中是否出现了以上3种情形。

    文章一:

    我想大多数人在学习多线程时都会对此问题有所顾虑,尽管多线程的概念不难理解,那我们什么时候该用它呢?在大多数情况下,我们写了程序,发现有时必须使用多线程才能得到理想的运行结果,于是我们按照资料调用相关的线程类库或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;
    	@Override
    	public void draw()
    	{
    		drawtank();//清除上一次的绘制,根据横坐标x画一个坦克
    		x+=speed;
    	}
    }
    

     

    子弹类:

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

     

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

     

        而且,如果想要添加游戏元素,例如其他类型的子弹,只需要实现Drawable接口即可。

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

     

        最后,我们对什么时候使用多线程,以及使用线程的数量做一个总结:在编写程序时,遇到了阻塞过程而不想使整个程序停止响应时,应使用多线程;一个程序的合理线程数量取决于对实际阻塞的抽象程度。

    原文参考:https://blog.csdn.net/shuzhe66/article/details/25484195

     

     

    文章二:

    如果你的应用程序需要采取以下的操作,那么你尽可在编程的时候考虑多线程机制:

    连续的操作,需要花费忍无可忍的过长时间才可能完成
    并行计算
    为了等待网络、文件系统、用户或其他I/O响应而耗费大量的执行时间
    所以说,在动手之前,先保证自己的应用程序中是否出现了以上3种情形。

    为什么需要多线程(解释何时考虑使用线程)
    从用户的角度考虑,就是为了得到更好的系统服务;从程序自身的角度考虑,就是使目标任务能够尽可能快的完成,更有效的利用系统资源。综合考虑,一般以下场合需要使用多线程:
    1、 程序包含复杂的计算任务时
    主要是利用多线程获取更多的CPU时间(资源)。
    2、 处理速度较慢的外围设备
    比如:打印时。再比如网络程序,涉及数据包的收发,时间因素不定。使用独立的线程处理这些任务,可使程序无需专门等待结果。
    3、 程序设计自身的需要
    WINDOWS系统是基于消息循环的抢占式多任务系统,为使消息循环系统不至于阻塞,程序需要多个线程的来共同完成某些任务。

    每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程。进程也可能是整个程序或者是部分程序的动态执行。线程是一组指令的集合,或者是程序的特殊段,它可以在程序里独立执行。也可以把它理解为代码运行的上下文。所以线程基本上是轻量级的进程,它负责在单个程序里执行多任务。通常由操作系统负责多个线程的调度和执行。

      什么是多线程?

      多线程是为了使得多个线程并行的工作以完成多项任务,以提高系统的效率。线程是在同一时间需要完成多项任务的时候被实现的。

      使用线程的好处有以下几点:

      ·使用线程可以把占据长时间的程序中的任务放到后台去处理

      ·用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度

      ·程序的运行速度可能加快

      ·在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下我们可以释放一些珍贵的资源如内存占用等等。

      还有其他很多使用多线程的好处,这里就不一一说明了。

      一些线程模型的背景

      我们可以重点讨论一下在Win32环境中常用的一些模型。

      ·单线程模型

      在这种线程模型中,一个进程中只能有一个线程,剩下的进程必须等待当前的线程执行完。这种模型的缺点在于系统完成一个很小的任务都必须占用很长的时间。

      ·块线程模型(单线程多块模型STA)

      这种模型里,一个程序里可能会包含多个执行的线程。在这里,每个线程被分为进程里一个单独的块。每个进程可以含有多个块,可以共享多个块中的数据。程序规定了每个块中线程的执行时间。所有的请求通过Windows消息队列进行串行化,这样保证了每个时刻只能访问一个块,因而只有一个单独的进程可以在某一个时刻得到执行。这种模型比单线程模型的好处在于,可以响应同一时刻的多个用户请求的任务而不只是单个用户请求。但它的性能还不是很好,因为它使用了串行化的线程模型,任务是一个接一个得到执行的。

      ·多线程块模型(自由线程块模型)

      多线程块模型(MTA)在每个进程里只有一个块而不是多个块。这单个块控制着多个线程而不是单个线程。这里不需要消息队列,因为所有的线程都是相同的块的一个部分,并且可以共享。这样的程序比单线程模型和STA的执行速度都要块,因为降低了系统的负载,因而可以优化来减少系统idle的时间。这些应用程序一般比较复杂,因为程序员必须提供线程同步以保证线程不会并发的请求相同的资源,因而导致竞争情况的发生。这里有必要提供一个锁机制。但是这样也许会导致系统死锁的发生。

    原文参考:http://blog.sina.com.cn/s/blog_4ee24ced0101375f.html

     

    文章三:

    “IO操作的DMA(Direct Memory Access)模式”开始讲起。DMA即直接内存访问,是一种不经过CPU而直接进行内存数据存储的数据交换模式。通过DMA的数据交换几乎可以不损耗CPU的资源。在硬件中,硬盘、网卡、声卡、显卡等都有DMA功能。CLR所提供的异步编程模型就是让我们充分利用硬件的DMA功能来释放CPU的压力。

    多线程使用的主要目的在于:


    1、吞吐量:你做WEB,容器帮你做了多线程,但是他只能帮你做请求层面的。简单的说,可能就是一个请求一个线程。或多个请求一个线程。如果是单线程,那同时只能处理一个用户的请求。

    2、伸缩性:也就是说,你可以通过增加CPU核数来提升性能。如果是单线程,那程序执行到死也就利用了单核,肯定没办法通过增加CPU核数来提升性能。

    鉴于你是做WEB的,第1点可能你几乎不涉及。那这里我就讲第二点吧。

    --举个简单的例子:
    假设有个请求,这个请求服务端的处理需要执行3个很缓慢的IO操作(比如数据库查询或文件查询),那么正常的顺序可能是(括号里面代表执行时间):
    a、读取文件1  (10ms)
    b、处理1的数据(1ms)
    c、读取文件2  (10ms)
    d、处理2的数据(1ms)
    e、读取文件3  (10ms)
    f、处理3的数据(1ms)
    g、整合1、2、3的数据结果 (1ms)
    单线程总共就需要34ms。
    那如果你在这个请求内,把ab、cd、ef分别分给3个线程去做,就只需要12ms了。

    所以多线程不是没怎么用,而是,你平常要善于发现一些可优化的点。然后评估方案是否应该使用。
    假设还是上面那个相同的问题:但是每个步骤的执行时间不一样了。
    a、读取文件1  (1ms)
    b、处理1的数据(1ms)
    c、读取文件2  (1ms)
    d、处理2的数据(1ms)
    e、读取文件3  (28ms)
    f、处理3的数据(1ms)
    g、整合1、2、3的数据结果 (1ms)
    单线程总共就需要34ms。
    如果还是按上面的划分方案(上面方案和木桶原理一样,耗时取决于最慢的那个线程的执行速度),在这个例子中是第三个线程,执行29ms。那么最后这个请求耗时是30ms。比起不用单线程,就节省了4ms。但是有可能线程调度切换也要花费个1、2ms。因此,这个方案显得优势就不明显了,还带来程序复杂度提升。不太值得。

    那么现在优化的点,就不是第一个例子那样的任务分割多线程完成。而是优化文件3的读取速度。
    可能是采用缓存和减少一些重复读取。
    首先,假设有一种情况,所有用户都请求这个请求,那其实相当于所有用户都需要读取文件3。那你想想,100个人进行了这个请求,相当于你花在读取这个文件上的时间就是28×100=2800ms了。那么,如果你把文件缓存起来,那只要第一个用户的请求读取了,第二个用户不需要读取了,从内存取是很快速的,可能1ms都不到。

     

    不使用线程池的缺点

    有些开发者图省事,遇到需要多线程处理的地方,直接new Thread(...).start(),对于一般场景是没问题的,但如果是在并发请求很高的情况下,就会有些隐患:

    • 新建线程的开销。线程虽然比进程要轻量许多,但对于JVM来说,新建一个线程的代价还是挺大的,决不同于新建一个对象
    • 资源消耗量。没有一个池来限制线程的数量,会导致线程的数量直接取决于应用的并发量,这样有潜在的线程数据巨大的可能,那么资源消耗量将是巨大的
    • 稳定性。当线程数量超过系统资源所能承受的程度,稳定性就会成问题

    原文参考:https://blog.csdn.net/wujizkm/article/details/50651808

    文章四:

    在不希望等待一个耗时任务的返回结果时,会涉及到多线程,比如下载三个文件,可以在当前线程开启三个新线程分别执行,而不影响当前线程继续运行
    在spring的项目中,一个请求就是一个线程

    为什么可以多线程以及多线程有什么意义?然后你就知道自己什么时候需要用到了。

    CPU从以前的单CPU单核发展为多核、多CPU、重核等,这是多线程可以实现的基础

    多线程即意味着多个任务(子任务)可以同时执行(当然,只是宏观上),若是单线程,只能一个接一个顺序执行。
    使用多线程,最直接的目的就是希望任务完成的更快。

    当然,多线程也会产生一些不希望看到的问题

    对java的线程类Thread来进行说明:
    1:Thread是针对是java其本身所具有的,但并不能说其没有调用操作系统,其最底层的时间片调度是按照操作系统来执行的。
    Thread下可以创建Thread,
    2个Thread在一定条件下也可以相互调用。
    根据以上特点可以总结认为java中的线程能让高级程序员更好的对庞大和复杂的数据流进行拆分,重组从而减低各个环节性能需求,通过增加各项负荷达到系统资源分配的最优值

    也就是为了更快

    对于处理时间短的服务或者启动频率高的要用单线程,相反用多线程!

     

    展开全文
  • 到底什么时候用多线程

    万次阅读 2014-05-10 17:40:56
    到底什么时候用多线程? 我想大多数人在学习多线程

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

        我想大多数人在学习多线程时都会对此问题有所顾虑,尽管多线程的概念不难理解,那我们什么时候该用它呢?在大多数情况下,我们写了程序,发现有时必须使用多线程才能得到理想的运行结果,于是我们按照资料调用相关的线程类库或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;
    	@Override
    	public void draw()
    	{
    		drawtank();//清除上一次的绘制,根据横坐标x画一个坦克
    		x+=speed;
    	}
    }
    子弹类:

    public class Bullet implements Drawable{
    	float x;//这里以横向移动为例子,只写一个属性
    	float speed = 10f;
    	@Override
    	public void draw()
    	{
    		drawbullet();//清除上一次的绘制,根据横坐标x画一个子弹
    		x+=speed;
    	}
    }
        我们可以发现:原有的实际阻塞过程已经被抽象到一个线程之中,而非阻塞过程,诸如绘制和坐标运算依然作为方法保留到对应类中,这样,无论有多少坦克和炮弹,只要非阻塞过程的运算压总和力不至于逼近阻塞的程度,使用一个线程即可完成所有工作。

        而且,如果想要添加游戏元素,例如其他类型的子弹,只需要实现Drawable接口即可。

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


        最后,我们对什么时候使用多线程,以及使用线程的数量做一个总结:在编写程序时,遇到了阻塞过程而不想使整个程序停止响应时,应使用多线程;一个程序的合理线程数量取决于对实际阻塞的抽象程度。

    展开全文
  • 系统接受实现多用户多请求的高并发时,通过多线程来实现。 二、线程后台处理大任务 一个程序是线性执行的。如果程序执行到要花大量时间处理的任务时,那主程序就得等待其执行完才能继续执行下面的。那用户就不得...

    本文转自:花和尚也有春天

    原文地址:https://blog.csdn.net/weixin_38750084/article/details/82911048

    一、高并发

    系统接受实现多用户多请求的高并发时,通过多线程来实现。

     

    二、线程后台处理大任务

    一个程序是线性执行的。如果程序执行到要花大量时间处理的任务时,那主程序就得等待其执行完才能继续执行下面的。那用户就不得不等待它执行完。

    这时候可以开线程把花大量时间处理的任务放在线程处理,这样线程在后台处理时,主程序也可以继续执行下去,用户就不需要等待。线程执行完后执行回调函数。

     

    三、大任务

    大任务处理起来比较耗时,这时候可以起到多个线程并行加快处理(例如:分片上传)。

    连续的操作,需要花费忍无可忍的过长时间才可能完成
    并行计算
    为了等待网络、文件系统、用户或其他I/O响应而耗费大量的执行时间
    所以说,在动手之前,先保证自己的应用程序中是否出现了以上3种情形。

    文章一:

    我想大多数人在学习多线程时都会对此问题有所顾虑,尽管多线程的概念不难理解,那我们什么时候该用它呢?在大多数情况下,我们写了程序,发现有时必须使用多线程才能得到理想的运行结果,于是我们按照资料调用相关的线程类库或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;

        @Override

        public void draw()

        {

            drawtank();//清除上一次的绘制,根据横坐标x画一个坦克

            x+=speed;

        }

    }
    子弹类:


    public class Bullet implements Drawable{

        float x;//这里以横向移动为例子,只写一个属性

        float speed = 10f;

        @Override

        public void draw()

        {

            drawbullet();//清除上一次的绘制,根据横坐标x画一个子弹

            x+=speed;

        }

    }

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

     

        而且,如果想要添加游戏元素,例如其他类型的子弹,只需要实现Drawable接口即可。

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

     

        最后,我们对什么时候使用多线程,以及使用线程的数量做一个总结:在编写程序时,遇到了阻塞过程而不想使整个程序停止响应时,应使用多线程;一个程序的合理线程数量取决于对实际阻塞的抽象程度。

    原文参考:https://blog.csdn.net/shuzhe66/article/details/25484195

    文章二:

    如果你的应用程序需要采取以下的操作,那么你尽可在编程的时候考虑多线程机制:

    连续的操作,需要花费忍无可忍的过长时间才可能完成
    并行计算
    为了等待网络、文件系统、用户或其他I/O响应而耗费大量的执行时间
    所以说,在动手之前,先保证自己的应用程序中是否出现了以上3种情形。

    为什么需要多线程(解释何时考虑使用线程)
    从用户的角度考虑,就是为了得到更好的系统服务;从程序自身的角度考虑,就是使目标任务能够尽可能快的完成,更有效的利用系统资源。综合考虑,一般以下场合需要使用多线程:
    1、 程序包含复杂的计算任务时
    主要是利用多线程获取更多的CPU时间(资源)。
    2、 处理速度较慢的外围设备
    比如:打印时。再比如网络程序,涉及数据包的收发,时间因素不定。使用独立的线程处理这些任务,可使程序无需专门等待结果。
    3、 程序设计自身的需要
    WINDOWS系统是基于消息循环的抢占式多任务系统,为使消息循环系统不至于阻塞,程序需要多个线程的来共同完成某些任务。

    每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程。进程也可能是整个程序或者是部分程序的动态执行。线程是一组指令的集合,或者是程序的特殊段,它可以在程序里独立执行。也可以把它理解为代码运行的上下文。所以线程基本上是轻量级的进程,它负责在单个程序里执行多任务。通常由操作系统负责多个线程的调度和执行。

      什么是多线程?

      多线程是为了使得多个线程并行的工作以完成多项任务,以提高系统的效率。线程是在同一时间需要完成多项任务的时候被实现的。

      使用线程的好处有以下几点:

      ·使用线程可以把占据长时间的程序中的任务放到后台去处理

      ·用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度

      ·程序的运行速度可能加快

      ·在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下我们可以释放一些珍贵的资源如内存占用等等。

      还有其他很多使用多线程的好处,这里就不一一说明了。

      一些线程模型的背景

      我们可以重点讨论一下在Win32环境中常用的一些模型。

      ·单线程模型

      在这种线程模型中,一个进程中只能有一个线程,剩下的进程必须等待当前的线程执行完。这种模型的缺点在于系统完成一个很小的任务都必须占用很长的时间。

      ·块线程模型(单线程多块模型STA)

      这种模型里,一个程序里可能会包含多个执行的线程。在这里,每个线程被分为进程里一个单独的块。每个进程可以含有多个块,可以共享多个块中的数据。程序规定了每个块中线程的执行时间。所有的请求通过Windows消息队列进行串行化,这样保证了每个时刻只能访问一个块,因而只有一个单独的进程可以在某一个时刻得到执行。这种模型比单线程模型的好处在于,可以响应同一时刻的多个用户请求的任务而不只是单个用户请求。但它的性能还不是很好,因为它使用了串行化的线程模型,任务是一个接一个得到执行的。

      ·多线程块模型(自由线程块模型)

      多线程块模型(MTA)在每个进程里只有一个块而不是多个块。这单个块控制着多个线程而不是单个线程。这里不需要消息队列,因为所有的线程都是相同的块的一个部分,并且可以共享。这样的程序比单线程模型和STA的执行速度都要块,因为降低了系统的负载,因而可以优化来减少系统idle的时间。这些应用程序一般比较复杂,因为程序员必须提供线程同步以保证线程不会并发的请求相同的资源,因而导致竞争情况的发生。这里有必要提供一个锁机制。但是这样也许会导致系统死锁的发生。

    原文参考:http://blog.sina.com.cn/s/blog_4ee24ced0101375f.html

     

    文章三:

    “IO操作的DMA(Direct Memory Access)模式”开始讲起。DMA即直接内存访问,是一种不经过CPU而直接进行内存数据存储的数据交换模式。通过DMA的数据交换几乎可以不损耗CPU的资源。在硬件中,硬盘、网卡、声卡、显卡等都有DMA功能。CLR所提供的异步编程模型就是让我们充分利用硬件的DMA功能来释放CPU的压力。

    多线程使用的主要目的在于:


    1、吞吐量:你做WEB,容器帮你做了多线程,但是他只能帮你做请求层面的。简单的说,可能就是一个请求一个线程。或多个请求一个线程。如果是单线程,那同时只能处理一个用户的请求。

    2、伸缩性:也就是说,你可以通过增加CPU核数来提升性能。如果是单线程,那程序执行到死也就利用了单核,肯定没办法通过增加CPU核数来提升性能。

    鉴于你是做WEB的,第1点可能你几乎不涉及。那这里我就讲第二点吧。

    --举个简单的例子:
    假设有个请求,这个请求服务端的处理需要执行3个很缓慢的IO操作(比如数据库查询或文件查询),那么正常的顺序可能是(括号里面代表执行时间):
    a、读取文件1  (10ms)
    b、处理1的数据(1ms)
    c、读取文件2  (10ms)
    d、处理2的数据(1ms)
    e、读取文件3  (10ms)
    f、处理3的数据(1ms)
    g、整合1、2、3的数据结果 (1ms)
    单线程总共就需要34ms。
    那如果你在这个请求内,把ab、cd、ef分别分给3个线程去做,就只需要12ms了。

    所以多线程不是没怎么用,而是,你平常要善于发现一些可优化的点。然后评估方案是否应该使用。
    假设还是上面那个相同的问题:但是每个步骤的执行时间不一样了。
    a、读取文件1  (1ms)
    b、处理1的数据(1ms)
    c、读取文件2  (1ms)
    d、处理2的数据(1ms)
    e、读取文件3  (28ms)
    f、处理3的数据(1ms)
    g、整合1、2、3的数据结果 (1ms)
    单线程总共就需要34ms。
    如果还是按上面的划分方案(上面方案和木桶原理一样,耗时取决于最慢的那个线程的执行速度),在这个例子中是第三个线程,执行29ms。那么最后这个请求耗时是30ms。比起不用单线程,就节省了4ms。但是有可能线程调度切换也要花费个1、2ms。因此,这个方案显得优势就不明显了,还带来程序复杂度提升。不太值得。

    那么现在优化的点,就不是第一个例子那样的任务分割多线程完成。而是优化文件3的读取速度。
    可能是采用缓存和减少一些重复读取。
    首先,假设有一种情况,所有用户都请求这个请求,那其实相当于所有用户都需要读取文件3。那你想想,100个人进行了这个请求,相当于你花在读取这个文件上的时间就是28×100=2800ms了。那么,如果你把文件缓存起来,那只要第一个用户的请求读取了,第二个用户不需要读取了,从内存取是很快速的,可能1ms都不到。

     

    不使用线程池的缺点

    有些开发者图省事,遇到需要多线程处理的地方,直接new Thread(...).start(),对于一般场景是没问题的,但如果是在并发请求很高的情况下,就会有些隐患:

    • 新建线程的开销。线程虽然比进程要轻量许多,但对于JVM来说,新建一个线程的代价还是挺大的,决不同于新建一个对象
    • 资源消耗量。没有一个池来限制线程的数量,会导致线程的数量直接取决于应用的并发量,这样有潜在的线程数据巨大的可能,那么资源消耗量将是巨大的
    • 稳定性。当线程数量超过系统资源所能承受的程度,稳定性就会成问题

    原文参考:https://blog.csdn.net/wujizkm/article/details/50651808

    文章四:

    在不希望等待一个耗时任务的返回结果时,会涉及到多线程,比如下载三个文件,可以在当前线程开启三个新线程分别执行,而不影响当前线程继续运行
    在spring的项目中,一个请求就是一个线程

    为什么可以多线程以及多线程有什么意义?然后你就知道自己什么时候需要用到了。

    CPU从以前的单CPU单核发展为多核、多CPU、重核等,这是多线程可以实现的基础

    多线程即意味着多个任务(子任务)可以同时执行(当然,只是宏观上),若是单线程,只能一个接一个顺序执行。
    使用多线程,最直接的目的就是希望任务完成的更快。

    当然,多线程也会产生一些不希望看到的问题

    对java的线程类Thread来进行说明:
    1:Thread是针对是java其本身所具有的,但并不能说其没有调用操作系统,其最底层的时间片调度是按照操作系统来执行的。
    Thread下可以创建Thread,
    2个Thread在一定条件下也可以相互调用。
    根据以上特点可以总结认为java中的线程能让高级程序员更好的对庞大和复杂的数据流进行拆分,重组从而减低各个环节性能需求,通过增加各项负荷达到系统资源分配的最优值

    也就是为了更快

    对于处理时间短的服务或者启动频率高的要用单线程,相反用多线程!

    展开全文
  • 什么多线程?如何实现多线程

    万次阅读 多人点赞 2019-04-09 09:53:36
    【转】什么线程安全?怎么实现线程安全?什么是进程?什么线程什么线程安全?添加一个状态呢?如何确保线程安全?synchronizedlock 转自:https://blog.csdn.net/csdnnews/article/details/82321777 什么是...


    转自:https://blog.csdn.net/csdnnews/article/details/82321777

    什么是进程?

    电脑中时会有很多单独运行的程序,每个程序有一个独立的进程,而进程之间是相互独立存在的。比如下图中的QQ、酷狗播放器、电脑管家等等。在这里插入图片描述

    什么是线程?

    进程想要执行任务就需要依赖线程。换句话说,就是进程中的最小执行单位就是线程,并且一个进程中至少有一个线程。

    那什么是多线程?提到多线程这里要说两个概念,就是串行和并行,搞清楚这个,我们才能更好地理解多线程。

    所谓串行,其实是相对于单条线程来执行多个任务来说的,我们就拿下载文件来举个例子:当我们下载多个文件时,在串行中它是按照一定的顺序去进行下载的,也就是说,必须等下载完A之后才能开始下载B,它们在时间上是不可能发生重叠的。
    在这里插入图片描述
    并行:下载多个文件,开启多条线程,多个文件同时进行下载,这里是严格意义上的,在同一时刻发生的,并行在时间上是重叠的。
    在这里插入图片描述
    了解了这两个概念之后,我们再来说说什么是多线程。举个例子,我们打开腾讯管家,腾讯管家本身就是一个程序,也就是说它就是一个进程,它里面有很多的功能,我们可以看下图,能查杀病毒、清理垃圾、电脑加速等众多功能。

    按照单线程来说,无论你想要清理垃圾、还是要病毒查杀,那么你必须先做完其中的一件事,才能做下一件事,这里面是有一个执行顺序的。

    如果是多线程的话,我们其实在清理垃圾的时候,还可以进行查杀病毒、电脑加速等等其他的操作,这个是严格意义上的同一时刻发生的,没有执行上的先后顺序。
    在这里插入图片描述
    以上就是,一个进程运行时产生了多个线程。

    在了解完这个问题后,我们又需要去了解一个使用多线程不得不考虑的问题——线程安全。

    今天我们不说如何保证一个线程的安全,我们聊聊什么是线程安全?因为我之前面试被问到了,说真的,我之前真的不是特别了解这个问题,我们好像只学了如何确保一个线程安全,却不知道所谓的安全到底是什么!

    什么是线程安全?

    当多个线程访问某个方法时,不管你通过怎样的调用方式、或者说这些线程如何交替地执行,我们在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以说这个类是线程安全的。
    既然是线程安全问题,那么毫无疑问,所有的隐患都是在多个线程访问的情况下产生的,也就是我们要确保在多条线程访问的时候,我们的程序还能按照我们预期的行为去执行,我们看一下下面的代码。

    Integer count = 0;
    public void getCount() {
           count ++;
           System.out.println(count);
     }
    

    很简单的一段代码,下面我们就来统计一下这个方法的访问次数,多个线程同时访问会不会出现什么问题,我开启的3条线程,每个线程循环10次,得到以下结果:
    在这里插入图片描述

    我们可以看到,这里出现了两个26,出现这种情况显然表明这个方法根本就不是线程安全的,出现这种问题的原因有很多。

    最常见的一种,就是我们A线程在进入方法后,拿到了count的值,刚把这个值读取出来,还没有改变count的值的时候,结果线程B也进来的,那么导致线程A和线程B拿到的count值是一样的。

    那么由此我们可以了解到,这确实不是一个线程安全的类,因为他们都需要操作这个共享的变量。其实要对线程安全问题给出一个明确的定义,还是蛮复杂的,我们根据我们这个程序来总结下什么是线程安全。

    当多个线程访问某个方法时,不管你通过怎样的调用方式、或者说这些线程如何交替地执行,我们在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以说这个类是线程安全的。

    搞清楚了什么是线程安全,接下来我们看看Java中确保线程安全最常用的两种方式。先来看段代码。

    public void threadMethod(int j) {
    
        int i = 1;
    
        j = j + i;
    }
    
    

    大家觉得这段代码是线程安全的吗?

    毫无疑问,它绝对是线程安全的,我们来分析一下,为什么它是线程安全的?

    我们可以看到这段代码是没有任何状态的,就是说我们这段代码,不包含任何的作用域,也没有去引用其他类中的域进行引用,它所执行的作用范围与执行结果只存在它这条线程的局部变量中,并且只能由正在执行的线程进行访问。当前线程的访问,不会对另一个访问同一个方法的线程造成任何的影响。

    两个线程同时访问这个方法,因为没有共享的数据,所以他们之间的行为,并不会影响其他线程的操作和结果,所以说无状态的对象,也是线程安全的。

    添加一个状态呢?

    如果我们给这段代码添加一个状态,添加一个count,来记录这个方法并命中的次数,每请求一次count+1,那么这个时候这个线程还是安全的吗?

    public class ThreadDemo {
    
       int count = 0; // 记录方法的命中次数
    
       public void threadMethod(int j) {
    
           count++ ;
    
           int i = 1;
    
           j = j + i;
       }
    }
    
    

    明显已经不是了,单线程运行起来确实是没有任何问题的,但是当出现多条线程并发访问这个方法的时候,问题就出现了,我们先来分析下count+1这个操作。

    进入这个方法之后首先要读取count的值,然后修改count的值,最后才把这把值赋值给count,总共包含了三步过程:“读取”一>“修改”一>“赋值”,既然这个过程是分步的,那么我们先来看下面这张图,看看你能不能看出问题:
    在这里插入图片描述
    可以发现,count的值并不是正确的结果,当线程A读取到count的值,但是还没有进行修改的时候,线程B已经进来了,然后线程B读取到的还是count为1的值,正因为如此所以我们的count值已经出现了偏差,那么这样的程序放在我们的代码中,是存在很多的隐患的。

    如何确保线程安全?

    既然存在线程安全的问题,那么肯定得想办法解决这个问题,怎么解决?我们说说常见的几种方式

    synchronized

    synchronized关键字,就是用来控制线程同步的,保证我们的线程在多线程环境下,不被多个线程同时执行,确保我们数据的完整性,使用方法一般是加在方法上。

    public class ThreadDemo {
    
       int count = 0; // 记录方法的命中次数
    
       public synchronized void threadMethod(int j) {
    
           count++ ;
    
           int i = 1;
    
           j = j + i;
       }
    }
    
    

    这样就可以确保我们的线程同步了,同时这里需要注意一个大家平时忽略的问题,首先synchronized锁的是括号里的对象,而不是代码,其次,对于非静态的synchronized方法,锁的是对象本身也就是this。

    当synchronized锁住一个对象之后,别的线程如果想要获取锁对象,那么就必须等这个线程执行完释放锁对象之后才可以,否则一直处于等待状态。

    注意点:虽然加synchronized关键字,可以让我们的线程变得安全,但是我们在用的时候,也要注意缩小synchronized的使用范围,如果随意使用时很影响程序的性能,别的对象想拿到锁,结果你没用锁还一直把锁占用,这样就有点浪费资源。

    lock

    先来说说它跟synchronized有什么区别吧,Lock是在Java1.6被引入进来的,Lock的引入让锁有了可操作性,什么意思?就是我们在需要的时候去手动的获取锁和释放锁,甚至我们还可以中断获取以及超时获取的同步特性,但是从使用上说Lock明显没有synchronized使用起来方便快捷。我们先来看下一般是如何使用的:

    private Lock lock = new ReentrantLock(); // ReentrantLock是Lock的子类
    
       private void method(Thread thread){
           lock.lock(); // 获取锁对象
           try {
               System.out.println("线程名:"+thread.getName() + "获得了锁");
               // Thread.sleep(2000);
           }catch(Exception e){
               e.printStackTrace();
           } finally {
               System.out.println("线程名:"+thread.getName() + "释放了锁");
               lock.unlock(); // 释放锁对象
           }
       }
    
    

    进入方法我们首先要获取到锁,然后去执行我们业务代码,这里跟synchronized不同的是,Lock获取的所对象需要我们亲自去进行释放,为了防止我们代码出现异常,所以我们的释放锁操作放在finally中,因为finally中的代码无论如何都是会执行的。

    写个主方法,开启两个线程测试一下我们的程序是否正常:

    public static void main(String[] args) {
           LockTest lockTest = new LockTest();
    
           // 线程1
           Thread t1 = new Thread(new Runnable() {
    
               @Override
               public void run() {
                   // Thread.currentThread()  返回当前线程的引用
                   lockTest.method(Thread.currentThread());
               }
           }, "t1");
    
           // 线程2
           Thread t2 = new Thread(new Runnable() {
    
               @Override
               public void run() {
                   lockTest.method(Thread.currentThread());
               }
           }, "t2");
    
           t1.start();
           t2.start();
       }
    
    

    结果
    在这里插入图片描述
    可以看出我们的执行,是没有任何问题的。

    其实在Lock还有几种获取锁的方式,我们这里再说一种,就是tryLock()这个方法跟Lock()是有区别的,Lock在获取锁的时候,如果拿不到锁,就一直处于等待状态,直到拿到锁,但是tryLock()却不是这样的,tryLock是有一个Boolean的返回值的,如果没有拿到锁,直接返回false,停止等待,它不会像Lock()那样去一直等待获取锁。

    我们来看下代码:

    private void method(Thread thread){
           // lock.lock(); // 获取锁对象
           if (lock.tryLock()) {
               try {
                   System.out.println("线程名:"+thread.getName() + "获得了锁");
                   // Thread.sleep(2000);
               }catch(Exception e){
                   e.printStackTrace();
               } finally {
                   System.out.println("线程名:"+thread.getName() + "释放了锁");
                   lock.unlock(); // 释放锁对象
               }
           }
       }
    
    

    结果:我们继续使用刚才的两个线程进行测试可以发现,在线程t1获取到锁之后,线程t2立马进来,然后发现锁已经被占用,那么这个时候它也不在继续等待。

    在这里插入图片描述
    似乎这种方法,感觉不是很完美,如果我第一个线程,拿到锁的时间,比第二个线程进来的时间还要长,是不是也拿不到锁对象?

    那我能不能,用一中方式来控制一下,让后面等待的线程,可以等待5秒,如果5秒之后,还获取不到锁,那么就停止等,其实tryLock()是可以进行设置等待的相应时间的。

    private void method(Thread thread) throws InterruptedException {
           // lock.lock(); // 获取锁对象
    
           // 如果2秒内获取不到锁对象,那就不再等待
           if (lock.tryLock(2,TimeUnit.SECONDS)) {
               try {
                   System.out.println("线程名:"+thread.getName() + "获得了锁");
    
                   // 这里睡眠3秒
                   Thread.sleep(3000);
               }catch(Exception e){
                   e.printStackTrace();
               } finally {
                   System.out.println("线程名:"+thread.getName() + "释放了锁");
                   lock.unlock(); // 释放锁对象
               }
           }
       }
    
    

    结果:看上面的代码,我们可以发现,虽然我们获取锁对象的时候,可以等待2秒,但是我们线程t1在获取锁对象之后,执行任务缺花费了3秒,那么这个时候线程t2是不在等待的。
    在这里插入图片描述
    我们再来改一下这个等待时间,改为5秒,再来看下结果:

    private void method(Thread thread) throws InterruptedException {
           // lock.lock(); // 获取锁对象
    
           // 如果5秒内获取不到锁对象,那就不再等待
           if (lock.tryLock(5,TimeUnit.SECONDS)) {
               try {
                   System.out.println("线程名:"+thread.getName() + "获得了锁");
               }catch(Exception e){
                   e.printStackTrace();
               } finally {
                   System.out.println("线程名:"+thread.getName() + "释放了锁");
                   lock.unlock(); // 释放锁对象
               }
           }
       }
    
    

    结果:这个时候我们可以看到,线程t2等到5秒获取到了锁对象,执行了任务代码。
    在这里插入图片描述
    以上就是使用Lock,来保证我们线程安全的方式。

    展开全文
  • 万字图解Java多线程

    万次阅读 多人点赞 2020-09-06 14:45:07
    前言 java多线程我个人觉得是javaSe中最难的一部分,我以前也是感觉学会了,但是真正有多线程的需求却不知道怎么下手,...什么是java多线程? 进程与线程 进程 当一个程序被运行,就开启了一个进程, 比如启动了qq,w.
  • 请按如下要求编写多线程程序: 1.得到一个随机的整数n,创建n个子线程对象; 2.要求在子线程中把当前线程的名称作为元素添加一个集合中; 3.当n个线程的名称都添加到集合中,遍历集合打印每个线程的名称; */...
  • Java多线程

    万次阅读 多人点赞 2021-06-11 16:28:49
    Java多线程Java多线程线程的创建线程常见方法线程的状态线程的优先级守护线程线程组Java线程池线程池的创建线程池的参数线程池的使用线程不安全问题Java中的锁synchronized同步方法synchronized同步语句块...
  • Java多线程学习(吐血超详细总结)

    万次阅读 多人点赞 2015-03-14 13:13:17
    本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。
  • 多线程什么ConcurrentHashMap

    千次阅读 2016-10-25 18:45:59
    上次我们说了一下项目中经常会出现的死锁问题,今天我们要说的是关于集合的问题,实际上跟锁也有一定的关系,让我们来一起看看吧。 这个类其实在我们项目中的缓存...不过我建议大家还是要看看源码,从中会受益很
  • Java多线程超详解

    万次阅读 多人点赞 2019-06-11 01:00:30
    随着计算机的配置越来越高,我们需要将进程进一步优化,细分为线程,充分提高图形化界面的多线程的开发。这就要求对线程的掌握很彻底。 那么话不多说,今天本帅将记录自己线程的学习。 线程的相关API //获取当前...
  • Java多线程详解

    千次阅读 多人点赞 2019-07-29 17:18:03
    今天我们聊一聊多线程,谈到多线程,很多人就开始难受,这是一个一听就头疼的话题,但是,我希望你在看完这篇文章后能对多线程有一个深入的了解。 案例 那么,首先我就举一个电影院卖票的例子来模拟多线程。 复仇者...
  • JAVA线程与多线程

    千次阅读 多人点赞 2016-08-25 19:10:10
    去安卓面试的时候通常会问一些java问题,所以呢你可能觉得答问题时...多线程的运行是根据CPU切换完成,如何切换由CPU决定,因此多线程运行具有不确定性。● 线程java中的线程使用java.lang.Thread类或者java.lang.Ru
  • 爬虫 第五讲 多线程爬虫

    万次阅读 2021-04-29 15:30:00
    爬虫 第五讲 多线程爬虫 一、多线程 1.多线程基本介绍 有很多的场景中的事情是同时进行的,比如开车的时候 手和脚共同来驾驶汽车,再比如唱歌跳舞也是同时进行的。 程序中模拟多任务 import time def sing(): for ...
  • 多线程

    千次阅读 2012-06-13 08:59:10
    多线程 进程:是一个正在执行的程序 每个进程都有一个执行顺序,该顺序是一个执行路径或叫一个控制单元, 线程:就是进程中的一个独立的控制单元。 线程在控制着进程的执行 一个进程中可以有多个线程, 但是一个...
  • 什么多线程 菜鸟入门

    万次阅读 2015-12-23 16:29:02
    网上很多资料对多线程都有详细的描述与理解,本菜鸟刚刚入门JAVA...1.什么多线程? 是指一个应用程序同时执行多个任务,一般来说一个任务就是一个线程 ,而一个应用程序有一个以上的线程我们称之为多线程。 2.什么
  • python 为什么io密集要用多线程

    千次阅读 2018-05-10 09:32:55
    python多线程在处理io的时候,一个线程获得GIL发送消息,然后等待返回消息(阻塞),python此时释放GIL, 其他线程得到GIL发送消息,然后同样等待返回消息(阻塞)......,这样保证了IO传输过程时间的合理利用,提高...
  • 我说我懂多线程,面试官立马给我发了offer

    万次阅读 多人点赞 2020-04-07 09:24:52
    我认为主要原因有两个: 多线程在初学的时候不太好学,并且一般写项目的时候也很少得上(至少在初学阶段时写的项目基本不需要自己创建线程)。 多线程的知识点在面试经常考,多线程所涉及的知识点非常多,难度也不...
  • 【java多线程编程】三种多线程的实现方式

    万次阅读 多人点赞 2019-01-01 16:20:56
    文章目录前言进程与线程继承Thread类,实现多线程FAQ 为什么多线程的启动不直接使用run()方法而必须使用Thread类中start()方法呢?基于Runnable接口实现多线程Thread 与 Runnable 的关系Callable实现多线程线程...
  • 秒杀多线程第五篇 经典线程同步 关键段CS

    万次阅读 多人点赞 2012-04-11 09:06:40
    上一篇《秒杀多线程第四篇 一个经典的多线程同步问题》提出了一个经典的多线程同步互斥问题,本篇将关键段CRITICAL_SECTION来尝试解决这个问题。本文首先介绍下如何使用关键段,然后再深层次的分析下关键段的实现...
  • Java多线程面试题

    万次阅读 2020-10-25 15:56:40
    sleep 方法: 是 Thread 类的静态方法,当前线程将睡眠 n 毫秒,线程进入阻塞状态。当睡眠时间到了,会解除阻塞,进行可运行状态,等待 CPU 的到来。睡眠不释放锁(如果有的话); wait 方法: 是 Object 的方法...
  • 教妹学 Java:难以驾驭的多线程

    千次阅读 多人点赞 2019-06-06 07:28:32
    多线程
  • 多线程技术

    千次阅读 2016-11-07 15:29:41
    多任务、多线程和多处理这些术语经常被交替地使用,但是它们在本质上是不同的概念。多任务是指操作系统具有在任务间快速切换使得这些任务看起来是在同步执行的能力。在一个抢占式多任务系统中,应用程序可以随时被...
  • c++多线程编程与MFC多线程编程

    千次阅读 2012-03-16 15:52:18
    源代码1:http://download.csdn.net/detail/nuptboyzhb/4160217 源码2:...   (一)有关多线程的WIN32 API函数 1、HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes
  • Java - 多线程

    万次阅读 2019-03-23 10:43:55
    分享一个大牛的人工智能教程。零基础!通俗易懂!...用多线程只有一个目的,那就是更好的利用CPU的资源,因为所有的多线程代码都可以单线程来实现。说这个话其实只有一半对,因为反应”多角色”的程序...
  • 多线程的代价

    千次阅读 2017-10-27 10:01:36
    而应该明确在使用多线程时能多来的好处比所付出的代价大的时候,才使用多线程。如果存在疑问,应该尝试测量一下应用程序的性能和响应能力,而不只是猜测。 设计更复杂 虽然有一些多线程应用程序比单线程...
  • 什么情况下使用多线程

    千次阅读 2017-04-03 17:42:17
    如果你的应用程序需要采取以下的操作,那么你尽可在编程的时候考虑多线程机制: 连续的操作,需要花费忍无可忍的过长时间才可能完成 并行计算 为了等待网络、文件系统、用户或其他I/O响应而耗费大量的执行时间 ...
  • 同步多线程

    千次阅读 2013-11-02 08:50:57
    同步多线程(SMT)是一种在一个CPU 的时钟周期内能够执行来自多个线程的指令的硬件多线程技术。本质上,同步多线程是一种将线程级并行处理(多CPU)转化为指令级并行处理(同一CPU)的方法。 同步多线程是单个物理...
  • C语言多线程

    千次阅读 2016-09-07 14:53:40
    C语言的相对于其他高级语言来说比较呆板,但是它也有比较好的多线程处理方法
  • Java多线程——什么是线程安全和线程不安全

    万次阅读 多人点赞 2016-08-09 14:39:33
    线程安全 就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全 就是不提供数据...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 495,088
精华内容 198,035
关键字:

多线程什么时候用得到