dtcms文档 新闻api editor SQLMAP regex datepicker requirejs angularjs视频教程 微信小游戏开发视频 mysql倒序 git登录命令 python3网络编程 python类和对象 python处理json文件 java入门编程 java获取年份 java的方法 java框架学习 javascript案例 php开发教程 asp建站系统 cmd代码 考试练习系统 dnf传说装备 kontakt 手机电脑模拟器 x64dbg 原创检测工具 电脑还原软件 jsp源代码 卧龙推广 ps怎么羽化图片边缘 易语言tv ps怎么做动画 调试工具 加字幕的软件 网红男头像 什么是内存条 ps怎么做圆角矩形 文件管理器
当前位置: 首页 > 学习教程  > 编程学习

十一、多线程的应用场景

2021/1/9 1:51:37 文章标签: 多线程的应用场景

我们有时貌似熟悉异步、多线程、任务和并行,但有时又不一定特别清楚它们之前的本质区别,甚至在很多复杂的场景下乱用一气。下面我就结合场景来说明在什么情况下该采用什么。同时,还讲解下如何优雅地控制线程,处理任务和并行中的异…

 

我们有时貌似熟悉异步、多线程、任务和并行,但有时又不一定特别清楚它们之前的本质区别,甚至在很多复杂的场景下乱用一气。下面我就结合场景来说明在什么情况下该采用什么。同时,还讲解下如何优雅地控制线程,处理任务和并行中的异常。

 

 一、在什么场景可以使用多线程

  • 想要同时处理多件事:单线程处理不了的,必须使用多线程。(类似于分身术)
  • 多个线程分解大任务:用单线程可以做,但是使用多线程可以更快。(类似于左右开弓)

例1:同时处理2件事

          场景描述: 执行实际工作20秒钟,20秒时间到结束执行。

public class CalculatePrimes extends Thread{
 //使用两个线程,一个用于计时(线程会休眠20秒然后设置一个主线程要检查的标志finished),一个用于执行实际工作。在执行实际工作的线程启动前启动计时线程。

//达到20秒钟主线程将停止。
 public static final int SECONDS = 20000;
 
 public volatile boolean finished = false;
 
 public void run(){
  System.out.println("开始执行实际工作啦");
  for (long i = 0l; i < Long.MAX_VALUE; i++) {
   if(finished){
    break;
   }
   if(i%100000000==0){
    System.out.println("i:"+i);
   }
  }
  System.out.println("结束执行实际工作啦");
 }
 
 public static void main(String[] args){
  //通过实例化CalculatePrimes类型的对象来创建线程。
  CalculatePrimes calculator = new CalculatePrimes();
  calculator.start();
  try{
   System.out.println("执行Thread.sleep前,时间为:"+System.currentTimeMillis());
   Thread.sleep(SECONDS);
   System.out.println("执行Thread.sleep后,时间为:"+System.currentTimeMillis());
  }catch(InterruptedException e){
   
  }
  calculator.finished = true;
  System.out.println("执行完main()");
 }
}

执行结果如下:

执行Thread.sleep前,时间为:1325663362824
开始执行实际工作啦
i:0
i:100000000
i:200000000
i:300000000
i:400000000
i:500000000
i:600000000
i:700000000
i:800000000
i:900000000
i:1000000000
i:1100000000
i:1200000000
i:1300000000
执行Thread.sleep后,时间为:1325663382824
结束执行实际工作啦
执行完main()

 或者用TimerTask来实现:

public class CalculatePrimes extends Thread{
 //使用两个线程,一个用于计时(线程会休眠10秒然后设置一个主线程要检查的标志finished),一个用于执行实际工作。在执行实际工作的线程启动之前,启动计时线程。达到10秒钟主线程将停止。
 public static final int SECONDS = 20000;
 
 public volatile boolean finished = false;
 
 public void run(){
  System.out.println("开始执行实际工作啦");
  for (long i = 0l; i < Long.MAX_VALUE; i++) {
   if(finished){
    break;
   }
   if(i%100000000==0){
    System.out.println("i:"+i);
   }
  }
  System.out.println("结束执行实际工作啦");
 }
  
 public static void main(String[] args){
  final CalculatePrimes calculator = new CalculatePrimes();
  calculator.start();
  
  Timer timer = new Timer();
  timer.schedule(
    new TimerTask(){
     public void run(){
      calculator.finished = true;
     }
    }, SECONDS);
 }
}

例2:计算密集型的一件事

应用场景:从一个非常大的数组中查找值最大的元素。

 

public class TenThreads {
 
 private static class WorkerThread extends Thread{
  int max = Integer.MIN_VALUE;
  
  int[] ourArray;
  
  public int getMax(){
   return max;
  }
  
  public WorkerThread(int[] ourArray){
   this.ourArray = ourArray;
   for (int i = 0; i < ourArray.length; i++) {
    System.out.print(ourArray[i]+"    ");
   }
   System.out.println();
  }
  
  public void run(){
   for (int i = 0; i < ourArray.length; i++) {
    max = Math.max(max,ourArray[i]);
   }
  }
  
 }
 
 public static void main(String[] args){
  WorkerThread[] threads = new WorkerThread[9];
  int[][] bigMatrix = {{1,10,100},{2,20,200},{3,33,333},{4,40,444},{5,50,500},{6,66,666},{7,77,777},{8,88,888},{9,99,999}};
  int max = Integer.MIN_VALUE;
  for (int i = 0; i < 9; i++) {
   threads[i] = new WorkerThread(bigMatrix[i]);
   threads[i].start();
  }
  
  try{
   for (int i = 0; i < 9; i++) {
    threads[i].join();
    max = Math.max(max,threads[i].getMax());
   }
  }catch(InterruptedException e){
   
  }
  System.out.println("Maximum value is:"+max);
 }
}

执行结果:

1    10    100   
2    20    200   
3    33    333   
4    40    444   
5    50    500   
6    66    666   
7    77    777   
8    88    888   
9    99    999   
Maximum value is:999

当然这个示例程序的数据量不大,只起到示例说明的作用,只要大家明白意图就好,大任务在实际工作中还是会遇到比较多的。

 

二、区分异步和多线程应用场景

举个场景--要获取某个网页的内容并显示出来。来看下应该选择异步还是多线程更适合。

可以预见,如果该网页的内容很多,或者当前的网络状况不太好,获取网页的过程会持续较长时间。

于是,我们可能会想到用 新起工作线程的方法来完成这项工作,这样在等待网页内容返回的过程中界面就不会被阻滞了。是的,上面的程序解决了界面阻滞的问题,但是,它高效吗?答案是:不。因为这样是为了获取网页,新起了一个工作线程,然后在读取网页的整个过程中,该工作线程始终被阻滞,直到获取网页完毕为止。在整个过程中,工作线程被占用着,这意味着系统的资源始终被消耗着、等待着。

如果使用异步模式去实现,它使用线程池进行管理。新起异步操作后,会将工作丢给线程池中的某个工作线程来完成。当开始I/O操作的时候,异步会将工作线程还给线程池,这意味着获取网页的工作不会再占用任何CPU资源了。直到异步完成,即获取网页完毕,异步才会通过回调的方式通知线程池。可见,异步模式借助于线程池,极大地节约了CPU的资源。

注:直接内存访问,是一种不经过CPU而直接进行内存数据存储的数据交换模式。通过直接内存访问的数据交换几乎可以不损耗CPU的资源。在硬件中,硬盘、网卡、声卡、显卡等都有D直接内存访问功能。异步编程模型就是让我们充分利用硬件的直接内存访问功能来释放CPU的压力。

明白了异步和多线程的区别后,我们来总结下两者的应用场景:

  • 计算密集型工作,采用多线程。
  • IO密集型工作,采用异步机制。

 

三、线程在现有其他技术中的应用

      在使用下面这些技术是,我们必须始终假设可以在多个线程中并发地执行的情境,这就要求我们必须适当同步共享数据。

  1. Servlet和JSP技术

        Servlet容器创建多个线程,在这些线程中执行servlet请求。在你写servlet程序时,你不需要知道你的servlet请求是在什么线程中执行的。但是你要知道,如果同时有多个对相同URL的请求入栈,那么同一个servlet可能会同时在多个线程中是活动的。这要求我们再编写servlet或jsp时,必须始终假设可以在多个线程中并发地执行同一个servlet或jsp的情境,这就要求我们必须适当同步servlet或jsp文件访问的任何共享数据,包括servlet对象本身的字段。

      2.    现实RMI对象

        RMI让你可以调用在其他JVM中运行的对象并对其进行操作。当调用远程方法时,RMI编译器创建的RMI存根会打包方法参数,并通过网络警它们发送到远程系统,然后远程系统会将它们解包并调用远程方法。

        假设你创建了一个RMI对象,并将它注册到RMI注册表或者JNDI名称空间(Java Naming and Directory Interface即Java命名和目录接口)。当远程客户机调用其中的一个方法是,该方法会在什么线程中执行呢?假设RMI对象的常用方法是继承UnicastRemoteObject,在构造UnicastRemoteObject时,会初始化用于分派远程方法调用的基础结构,这包括用于接收远程调用请求的套接字侦听器和一个或多个执行远程请求的线程。所以,当接收到执行RMI方法的请求时,这些方法将在RMI管理的线程中执行。

       3.Collection集合类

       Collection集合类在自身实现时并没有使用同步,这就意味着程序员在使用集合类时,在多线程的情境下如果没有进行同步,是有可能出问题的,即不能在不进行同步的情况下在多线程场景下使用集合类。通过每次访问共享集合中的方法时使用同步,可以在多线程应用程序中使用Collection类。对于任何给定的集合,每次必须用同一个锁进行同步。通常可以选择集合对象本身作为锁。另一种方法是使用Collections类提供的一组List、Map、Set的封装器。如可以用Collections.synchronizedMap封装Map,它将确保所有对该映射的访问都被正确同步了。

         

              


 


本文链接: http://www.dtmao.cc/news_show_1100015.shtml

附件下载

相关教程

    暂无相关的数据...

共有条评论 网友评论

验证码: 看不清楚?