为什么需要多线程

作者: 憨憨二师兄 | 来源:发表于2020-07-25 13:13 被阅读0次
  • 为什么需要多线程

    • 《CPU:这个世界太慢了》 现代CPU的速度一般为3GHz,从内存中读取的速度为10μs,从磁盘中读写的速度更慢,网络IO的速度对CPU来说等同与我们人类的好几万年
    • 现代CPU都是多核的
    • Java的执行模型是同步/阻塞(block)的
    • 默认情况下只有一个线程
      • 处理问题非常自然

      • 但是具有严重的性能问题

        示例程序:向临时文件中写数据

        import java.io.File;
        import java.io.FileOutputStream;
        import java.io.IOException;
        import java.io.UncheckedIOException;
        
        public class Crawler {
            public static void main(String[] args) {
                long t0 = System.currentTimeMillis();
                slowFileOperation();
                long t1 = System.currentTimeMillis();
                System.out.println("耗时:" + (t1 - t0) + "ms");
            }
        
            private static void slowFileOperation() {
                try {
                    File tmp = File.createTempFile("tmp", "");
        
                    for(int i = 0; i < 10000;i++){
                        FileOutputStream fos = new FileOutputStream(tmp);
                        fos.write(i);
                    }
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
            }
        }
        

        在我的主机上,执行的时间为:

        耗时:4001ms
        

        假设,有四个用户进行了请求,单线程处理这样的操作:

        import java.io.File;
        import java.io.FileOutputStream;
        import java.io.IOException;
        import java.io.UncheckedIOException;
        
        public class Crawler {
            public static void main(String[] args) {
                long t0 = System.currentTimeMillis();
                slowFileOperation();
                slowFileOperation();
                slowFileOperation();
                slowFileOperation();
                long t1 = System.currentTimeMillis();
                System.out.println("耗时:" + (t1 - t0) + "ms");
            }
        
            private static void slowFileOperation() {
                try {
                    File tmp = File.createTempFile("tmp", "");
        
                    for(int i = 0; i < 10000;i++){
                        FileOutputStream fos = new FileOutputStream(tmp);
                        fos.write(i);
                    }
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
            }
        }
        

        因为该代码是单线程,具有同步/阻塞的特点,所以代码会顺序执行,耗时:

        耗时:18821ms
        
  • 什么是线程

    • 开启一个新的线程

      Thread

      • Java中只有这么一种东西代表线程
      • start方法才能并发执行
      • 每多开一个线程,就多一个执行流
      • 方法栈(局部变量)是线程私有的
      • 静态变量/类变量是被所有线程共享的

      示例程序:

      import java.io.File;
      import java.io.FileOutputStream;
      import java.io.IOException;
      import java.io.UncheckedIOException;
      
      public class Crawler {
          public static void main(String[] args) {
              long t0 = System.currentTimeMillis();
              slowFileOperation();
              new Thread(Crawler::slowFileOperation).start();
              new Thread(Crawler::slowFileOperation).start();
              new Thread(Crawler::slowFileOperation).start();
              long t1 = System.currentTimeMillis();
              System.out.println("耗时:" + (t1 - t0) + "ms");
          }
      
          private static void slowFileOperation() {
              try {
                  File tmp = File.createTempFile("tmp", "");
      
                  for(int i = 0; i < 10000;i++){
                      FileOutputStream fos = new FileOutputStream(tmp);
                      fos.write(i);
                  }
              } catch (IOException e) {
                  throw new UncheckedIOException(e);
              }
          }
      }
      

      执行结果:

      耗时:3953ms
      

      可以看到大大缩短了时间

      run和start的区别?

      start方法才是开启了一个线程,而如果调用new Thread(...).run()和单线程调用普通的方法没有区别,示例程序:

      import java.io.File;
      import java.io.FileOutputStream;
      import java.io.IOException;
      import java.io.UncheckedIOException;
      
      public class Crawler {
          public static void main(String[] args) {
              long t0 = System.currentTimeMillis();
              slowFileOperation();
              new Thread(Crawler::slowFileOperation).run();
              new Thread(Crawler::slowFileOperation).run();
              new Thread(Crawler::slowFileOperation).run();
              long t1 = System.currentTimeMillis();
              System.out.println("耗时:" + (t1 - t0) + "ms");
          }
      
          private static void slowFileOperation() {
              try {
                  File tmp = File.createTempFile("tmp", "");
      
                  for(int i = 0; i < 10000;i++){
                      FileOutputStream fos = new FileOutputStream(tmp);
                      fos.write(i);
                  }
              } catch (IOException e) {
                  throw new UncheckedIOException(e);
              }
          }
      }
      
      

      执行结果:

      耗时:15248ms
      

      和单线程执行的结果无区别。

    • 线程难的本质?

      线程难的本质原因是你要看着同一份代码,想象不同的人在疯狂地以乱序执行它

      示例程序:

      
      public class Crawler {
      
          private static int i = 0;
      
          public static void main(String[] args) {
              new Thread(Crawler::modifySharedVariable).start();
              new Thread(Crawler::modifySharedVariable).start();
              new Thread(Crawler::modifySharedVariable).start();
              new Thread(Crawler::modifySharedVariable).start();
          }
      
          private static void modifySharedVariable() {
              i++;
              System.out.println("i : " + i);
          }
      }
      

      该段程序执行的结果为:

      [图片上传失败...(image-748785-1595653899958)]

      看上去没有问题,如果我们对代码进行一些修改,开启更多的线程,并在在run方法中设置线程睡眠1ms的时候,鬼畜的事情就发生了:

      
      public class Crawler {
      
          private static int i = 0;
      
          public static void main(String[] args) {
      
              for(int i = 0; i < 10; i++){
                  new Thread(Crawler::modifySharedVariable).start();
              }
          }
      
          private static void modifySharedVariable() {
              try {
                  Thread.sleep(1);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              i++;
              System.out.println("i : " + i);
          }
      }
      
      

      该段程序执行的结果为(每次执行结果有可能不同):

      i : 4
      i : 4
      i : 4
      i : 4
      i : 5
      i : 4
      i : 4
      i : 6
      i : 8
      i : 8
      
      

      那么为什么会发生这样的情况呢?
      因为i++这个操作并不是一个原子操作,它实际上可以分解成三件事情:

      • 将i的值拿到
      • 将i的值+1
      • 将i的值写回到内存

      试想有两个线程,线程A首先拿到i的值假设为0,然后将i的值加1,这时候cpu给线程A的时间片到了,线程A被阻塞,线程B开始执行:拿到i的值仍然为0,然后执行i+1的操作,然后将i的值也就是1写回到内存;然后线程A又开始执行:执行了写的操作又将1写回到内存。所以才会出现上面这样鬼畜的事情!!

  • 多线程的适用场景

    CPU密集型与IO密集型

    1. CPU密集型(cpu intense)

      典型的操作:编解码,数学的矩阵操作这些操作都需要CPU不停地去运算;对于CPU密集型不适合使用多线程,或者说多线程对于CPU密集型提升很小,多线程就是希望CPU不要闲着。

    2. IO密集型

      多线程对于IO密集型操作及其有用!典型操作例如:文件IO,网络IO(通常也包括数据库);因为网络IO传输的时间要比你CPU运算时间多很多,我们希望CPU这个时候能够充分地利用起来,就像你在烧水的时候,还可以切菜,倒油等等

    3. 多线程对于性能提升的上限在哪里?

      • 单核 CPU 100%
      • 多核 CPU N*100%

相关文章

  • 2018-12-11

    10. HashMap在多线程环境下使用需要注意什么?为什么? 在多线程环境下,需要使用ConcurrentHas...

  • 浅谈iOS中多线程开发

    目录: (一)线程与进程之间的区别 (二)为什么需要学习多线程 (三)多线程任务执行方式 (四)多线程执行的...

  • 多线程与并发原理

    为什么需要多线程 Java的执行模型是同步/阻塞(BLOCK)的。 默认情况下只有一个线程,因此需要多线程来提高效...

  • 【为什么需要多线程】

    本文代码会在后期整理上传到github中 1. 没有使用多线程情况 代码: 输出: 2017-01-14 21:3...

  • 为什么需要多线程

    为什么需要多线程《CPU:这个世界太慢了》 现代CPU的速度一般为3GHz,从内存中读取的速度为10μs,从磁盘中...

  • Android(安卓)多线程原理理解

    为什么我们需要使用多线程 提高用户体验 避免ANR(Application is not responding) ...

  • Springboot配置多线程定时任务Schedule

    一.为什么需要配置多线程定时任务& 多线程定时任务的配置使用场景 springboot中通过注解 @Schedul...

  • IOS 多线程

    多线程为什么需要多线程? 将太多耗费时间的操作放在主线程执行,会造成主线程堵塞,带来极差的用户体验。多线程是防止主...

  • 多线程学习(上)

    为什么需要多线程? CPU/内存/IO巨大的性能差异; 多核CPU的发展; 多线程可以使得多个执行流,并发执行。 ...

  • [iOS] 线程锁 — synchronized & 各种Loc

    1. 为什么多线程需要锁? 首先在多线程处理的时候我们经常会需要保证同步,这是为啥呢,看一下下面这个例子: 这种时...

网友评论

    本文标题:为什么需要多线程

    本文链接:https://www.haomeiwen.com/subject/plgplktx.html