28中级 - Java多线程原理【爬虫项目实战】

181 阅读3分钟

为什么需要多线程

  • 所有的对象数据都在堆上面分配,变量只是一个指针而已
  • Java的执行模型是同步/阻塞(block)的
  • 发热量制约了电脑cpu赫兹的增长,只能堆核心
  • 默认的main线程,默认就是一个线程

1.png

  • 测试耗时
package com.github.hcsp.io;

![1.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/00165a6cb10e4da7a6e796b570646d69~tplv-k3u1fbpfcp-watermark.image?)

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++) {
                try (FileOutputStream fos = new FileOutputStream(tmp)) {
                    fos.write(i);
                }
            }
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }
}

线程最简单的方式

  • 最简单的使用线程的方法
package com.github.hcsp.io;


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();
        // 声明线程,相当于雇佣一个工人,发给他一个说明书
        new Thread(Crawler::slowFileOperation).start();
        new Thread(Crawler::slowFileOperation).start();
        new Thread(Crawler::slowFileOperation).start();
//        slowFileOperation();
//        slowFileOperation();
//        slowFileOperation();
        slowFileOperation();
        long t1 = System.currentTimeMillis();
        System.out.println("耗时" + (t1 - t0) + "ms");
    }

    private static void slowFileOperation() {
        System.out.println("I am running");
        try {
            File tmp = File.createTempFile("tmp", "");
            for (int i = 0; i< 10000; i++) {
                try (FileOutputStream fos = new FileOutputStream(tmp)) {
                    fos.write(i);
                }
            }
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

}

2.png

  • 其原理图

3.png

start方法才能开始执行并发

  • start和run的区别,start才能并发执行,相当于雇佣一个工人,发给他一个说明书,run相当于等这个工人执行完了之后再去做下一件事情
  • 每多开一个线程,就多一个执行流
  • 方法栈是线程私有的 4.png
  • 静态变量/类变量是被所有线程共享的

多线程共享资源是多线程各种坑的来源

  • 共享静态变量的例子,i被改变了4次
package com.github.hcsp.io;


import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UncheckedIOException;

public class Crawler {
    public static int i = 0;

    public static void main(String[] args) {
        long t0 = System.currentTimeMillis();
        // 声明线程,相当于雇佣一个工人,发给他一个说明书
        new Thread(Crawler::slowFileOperation).start();
        new Thread(Crawler::slowFileOperation).start();
        new Thread(Crawler::slowFileOperation).start();
        slowFileOperation();
        long t1 = System.currentTimeMillis();
        System.out.println("耗时" + (t1 - t0) + "ms");
    }

    private static void slowFileOperation() {
        System.out.println("I am running");
        i++;
        System.out.println("I am i->" + i);
        try {
            File tmp = File.createTempFile("tmp", "");
            for (int i = 0; i< 10000; i++) {
                try (FileOutputStream fos = new FileOutputStream(tmp)) {
                    fos.write(i);
                }
            }
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

}

线程难的本质

  • 看着同一份代码,想象不同的人在疯狂地以乱序执行它
package com.github.hcsp.io;


public class Crawler {
    public static int i = 0;

    public static void main(String[] args) {
        long t0 = System.currentTimeMillis();
        // 声明线程,相当于雇佣一个工人,发给他一个说明书
        for (int j = 0; j < 1000; j++) {
            new Thread(Crawler::modifySharedValues).start();
        }
        long t1 = System.currentTimeMillis();
        System.out.println("耗时" + (t1 - t0) + "ms");
    }

    private static void modifySharedValues() {
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // i++ 不是原子操作
        // 取 i 的值
        // 把 i 的值加1
        // 把修改后的值写回i
        i++;
        System.out.println("i->" + i);
    }

}

  • 多个线程访问一个变量由于这个变量不是原子的,就有可能是正常有可能不正常

5.png

多线程带来的性能提升

  • 对于IO密集型应用及其有用
    • 网络IO(通常包括数据库)
    • 文件IO
  • 对于CPU密集型应用稍有折扣
  • 性能提升的上线在单核CPU100%,多核就是N*100%