Java 之旅

中级08 - Java多线程原理

2019-09-27  本文已影响0人  晓风残月1994

多线程赋予了计算机同时完成很多件事情的能力。这等价于将它的计算能力提高了许多倍。

一、为什么需要多线程

主要是 CPU 同学太优秀了,能者多劳,能力越大,责任越大,CPU 不 996 甚至 007 真的是太浪费了,以辛勤劳动为荣,以好逸恶劳为耻。

其次,Java 的执行模型是同步/阻塞的,Java 程序默认情况下只有一个线程来处理问题,这非常自然且符合人类直觉。但诸如 IO 此等耗时操作,存在严重性能问题,所以可以开启多个线程来干活。

以下单线程代码,在我的机器上用时9231ms:

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

public class Test {

    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", "");
            try (FileOutputStream fos = new FileOutputStream(tmp)) {
                for (int i = 0; i < 100000; i++) {
                    fos.write('a');
                }
            }
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

}

二、开启一个新线程

同样的测试块,但是分别使用不同线程同时进行,现在除了主线程外,还分别启动了3个线程,实现了Runnable接口的run()方法是新线程的入口,就像主线程的main()方法一样。

    public static void main(String[] args) {
        long t0 = System.currentTimeMillis();
       // 写法1
        new Thread(new Runnable() {
            @Override
            public void run() {
                slowFileOperation();
            }
        }).start();
        // 写法2
        new Thread(() -> slowFileOperation()).start();
        // 写法3
        new Thread(Test::slowFileOperation).start();

        slowFileOperation();

        long t1 = System.currentTimeMillis();
        System.out.println("用时" + (t1 - t0) + "ms");
    }
thread
开启多个线程后,本次用时4742ms。

查看一下Thread的源码可知,调用Thread实例的start()方法会开始执行该线程,JVM 会调用该线程的run()方法,而run()内部则会尝试调用Runnable中的run()方法。最后会多一个执行流,有自己的方法栈,方法栈是线程私有的。

三、为什么多线程难

同一份代码被多个线程执行时,无法保证线程执行顺序,如果存在共享变量,那么更加无法保证最终结果。

CPU的速度相比内存和硬盘来说太快了,存在多线程时,把每个线程想象成钟表上的一个刻度,CPU就是不停高速旋转的指针,每个线程只被执行了片刻(实际是纳秒级别的切换速度),CPU 又立即执行下一个线程了,因为CPU足够快,所以从使用者角度才一般察觉不到这种切换。

看下面多线程的例子,其中由于共享变量的非原子操作导致输出乱序问题:

public class Test {
    private static int i = 0;

    public static void main(String[] args) {
        long t0 = System.currentTimeMillis();

        for (int j = 0; j < 100; j++) {
            new Thread(Test::slowFileOperation).start();
        }

        long t1 = System.currentTimeMillis();
        
        System.out.println("用时" + (t1 - t0) + "ms");
    }

    private static void modifySharedVariable() {
        i++;
        System.out.println("i = " + i);
    }
}

因为i++并不是一个原子操作,CPU 执行时分为三步:

取 i 的值
把 i 的值加 1
把修改后的值写回 i

程序运行过程中,CPU 可能会切换到别的线程,所以最终i的输出无法保证。

四、多线程的适用场景

计算机操作一般分为两种类型:

上一篇 下一篇

猜你喜欢

热点阅读