-
为什么需要多线程
- 《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密集型
-
CPU密集型(cpu intense)
典型的操作:编解码,数学的矩阵操作这些操作都需要CPU不停地去运算;对于CPU密集型不适合使用多线程,或者说多线程对于CPU密集型提升很小,多线程就是希望CPU不要闲着。
-
IO密集型
多线程对于IO密集型操作及其有用!典型操作例如:文件IO,网络IO(通常也包括数据库);因为网络IO传输的时间要比你CPU运算时间多很多,我们希望CPU这个时候能够充分地利用起来,就像你在烧水的时候,还可以切菜,倒油等等
-
多线程对于性能提升的上限在哪里?
- 单核 CPU 100%
- 多核 CPU N*100%
-
网友评论