美文网首页Java 之旅
中级08 - Java多线程原理

中级08 - Java多线程原理

作者: 晓风残月1994 | 来源:发表于2019-09-27 17:25 被阅读0次

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

    • 为什么需要多线程
    • 开启一个新线程
    • 为什么多线程难
    • 多线程的适用场景

    一、为什么需要多线程

    主要是 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的输出无法保证。

    四、多线程的适用场景

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

    • CPU 密集型:多线程带来的提升有限,比如解压缩文件;
    • IO 密集型:IO 操作之慢在 CPU 眼中仿佛是静止一般,CPU 当然不能干等着 IO 操作,要继续去做别的事,所以适合采用多线程技术。

    相关文章

      网友评论

        本文标题:中级08 - Java多线程原理

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