为什么需要多线程

作者: 憨憨二师兄 | 来源:发表于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%

    相关文章

      网友评论

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

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