为什么需要多线程

  • 为什么需要多线程

    • 《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%
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260