简述Java多线程与lambda表达式

概述

首先要介绍进程与线程的概念。
所谓进程,即在一个系统中,每一个程序就是一个进程。而这个程序又可以拥有 >1 的线程。比如一个浏览器,你可以打开一个网页进行播放音乐,也可以打开一个网页播放视频。那么简单的来说,每一个网页都是单独的一个线程。他们可以分开进行工作。
所谓左手画方,右手画圆,两能不成,如果我们同时需要进行两个任务,那么就需要掌握线程方面的知识了。
不过值得说明的是,一般情况下线程并不是意味着同时执行,而是CPU运算速度很快,给你同步的错觉~

常见线程调用方式

继承Thread

继承Thread后可以重写它下面的run()方法,然后获得ThreadDemo这个对象类,就可以使用.start()方法启动了。

public class ThreadDemo extends Thread{
    @Override
    public void run() {
        System.out.println(1);
    }

    public static void main(String[] args) {
        ThreadDemo t1 = new ThreadDemo();
        t1.start();
    }
}

不过这个只是一个简单的demo,并不能体现出线程的用处。接下来我们思考一下,如果我们在线程中循环500次print,而在主线程(main)中也循环500次print,那么结果如何呢?
Snipaste_2020-04-01_11-25-49.png
很显然,因为我们创建的线程独立与我们的主线程,所以他们会出现”同时“
执行命令的状况,而不是先执行谁/后执行谁。

多线程下载器

既然我们学习了前面的知识那么就着手写一个小Demo吧!
我们的目的是: 在一个时间段内同时下载三张图片。
这里就不阐述如何进行下载了,我们调用一个叫做commons-io的工具类,它的下面拥有一个copyURLToFile()方法,它可以下载对应url中的内容,并且保存到本地。

     static class Download {

        public void download(String url, String name) {
            try {
                FileUtils.copyURLToFile(new URL(url), new File(name));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }


    }

我们在线程的run()方法中这样写:

        @Override
    public void run() {
        Download download = new Download();
        download.download(url, name);
        System.out.println(name + " -> 下载完毕!");
    }

在主线程中创建三个对象,即三个线程:

        public static void main(String[] args) {
        String url = "https://ku-m.cn/logo.jpg";
        webDownloadTest w1 = new webDownloadTest(url, "yy1.jpg");
        webDownloadTest w2 = new webDownloadTest(url, "yy2.jpg");
        webDownloadTest w3 = new webDownloadTest(url, "yy3.jpg");
        w1.start();
        w2.start();
        w3.start();

    }

Snipaste_2020-04-01_11-55-14.png
可以看到,它的顺序并不是按照我们初定的:1、2、3进行执行,而是由那个线程先创建速度以及下载速度而决定的。

Runnable接口

前面介绍了继承Thread的方式,可能你就会发现,如果那样编写岂不是一个对象只能生成一个线程?如果我想要下载一个文件三次该怎么办(假设有这个需求)。
所以我们一般并不是使用继承Thread的方式进行开启线程,而是调用runnable接口~
那么我们更改一下下载器吧。
我们只需要这样更改:

        String url = "https://ku-m.cn/logo.jpg";
        webDownloadTest w1 = new webDownloadTest(url, "yy1.jpg");
        new Thread(w1).start();
        new Thread(w1).start();
        new Thread(w1).start();

因为new Thread()中传输过去的参数就是一个runnable,所以我们可以直接这样调用。

Callable方式

与以上方式的区别是,当使用了callable<>接口之后,run()方法被替换为call()方法,不过好处是,可以拥有一个返回值。


    @Override
    public Boolean call() throws Exception {
        webDownload wd = new webDownload();
        wd.download(fileUrl, fileName);
        System.out.println(fileName + "下载完毕。");
        return true;
    }

在执行这个线程的步骤也发生了改变,我们需要先创建一个线程池,然后把线程对象提交过去。

        String url = "https://www.baidu.com/img/bd_logo1.png";
        callable t1 = new callable(url, "1.jpg");
        callable t2 = new callable(url, "2.jpg");
        callable t3 = new callable(url, "3.jpg");
        ExecutorService ser = Executors.newFixedThreadPool(3);
        Future<Boolean> r1 = ser.submit(t1);
        Future<Boolean> r2 = ser.submit(t2);
        Future<Boolean> r3 = ser.submit(t3);
        System.out.println(r1.get());
        System.out.println(r2.get());
        System.out.println(r3.get());

可以通过.get()方法获得一个返回值。
这种方法暂时没有深入研究,后面学到了会进行补充。

补充

经过后面学习,发现线程池是在一开始直接初始化那些数量的线程,所以后期无需等待就可以直接运行!并且经过测试,使用线程池后的线程速度更快。

        long start = System.currentTimeMillis();
        CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
        ExecutorService ex = Executors.newFixedThreadPool(1000);
        for (int i = 0; i < 10000; i++) {
            ex.execute(()->{
                list.add(1);
            });
        }
        long end = System.currentTimeMillis();

        System.out.println("耗时:" + (end - start) + "毫秒");
        ex.shutdown();

以上代码结果为最大100毫秒,而正常的new Thread()则是600毫秒。

游戏:龟兔赛跑

下面我们使用多线程的思想来抽象化一个游戏龟兔赛跑,可能你直接就会想到,我们创建两个线程,其中一个为”兔子“,另外一个为”乌龟“。比那个for循环先增加到100,为成功。

按照原定故事,兔子每走10步,我们给它用Thread.sleep()函数将线程暂停10毫秒。

public class Races implements Runnable {
    private static String winner;

    @Override
    public void run() {

        for (int i = 0; i <= 100; i++) {
            if (Thread.currentThread().getName().equals("兔子") && i % 10 == 0) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            boolean flag = gameOver(i);
            if (flag) {
                break;
            }
            System.out.println(Thread.currentThread().getName() + "跑到了 => " + i);
        }


    }

    public boolean gameOver(int steps) {
        if (winner != null) {
            return true;
        } else {
            if (steps >= 100) {
                winner = Thread.currentThread().getName();
                System.out.println("winner is " + winner);
                return true;
            }

        }
        return false;

    }

    public static void main(String[] args) {
        Races races = new Races();
        new Thread(races, "兔子").start();
        new Thread(races, "乌龟").start();
    }


}

值得说明的是,当我们new THread(reces,)后,是拥有第二个参数的,这个参数名是”name“,可以为这个线程进行命名。然后在线程中可以使用Thread.currentThread().getname()函数进行获得。

lambda表达式

这个表达式的意义是——当借口下只有一个方法的时候,我们可以大幅度的简写无意义的代码,而保留主要逻辑代码。
使用它可以去掉引入runnable接口以及重写run方法的流程。
可以直接通过类名 名字 = (参数)-> {}; 这样的方式进行编写。和JavaScript中的es6语法很是相似。
因此,我们的下载器代码就可以更改为:

    String url = "https://www.baidu.com/img/bd_logo1.png";
    String[] fileName = {"1.jpg","2.jpg","3.jpg"};

    for (int i = 0; i < fileName.length; i++) {
        final int temp = i;
        Runnable r = ()->{
            webDownload wd = new webDownload();
            wd.download(url, fileName[temp]);
            System.out.println(fileName[temp] + "下载完毕。");
        };

        new Thread(r).start();
    }

线程停止

一般情况下并不建议使用jdk中自带的线程停止方法,像stop()、destroy()方法都是属于已经废弃的。一般使用一个标志位,当主线程更改标志位后,主动让线程停止。
我们在之前的”龟兔赛跑“游戏之中,其实就已经使用了这种思想。

     boolean flag = gameOver(i);
        if (flag) {
            break;
        }

线程礼让

含义是,一个线程创建完毕之后一直执行它的内容,通过Thread.yield()函数可以暂时停止这个线程,转而让后面那个线程优先执行。不过这一切都是由CPU进行自动调度,无法认为操作。就是说,有的时候会成功,有的时候不会。
听完课感觉这个有些鸡肋?暂时不清楚有什么意义。
Snipaste_2020-04-01_17-11-55.png

        Runnable r1 = () -> {

        while (true) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(1);
            Thread.yield();
        }
    };
    Runnable r2 = () -> {

        while (true) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(2);
            Thread.yield();
        }

    };
    new Thread(r1).start();
    new Thread(r2).start();

优先执行

线程中内置了join()函数用来优先让某一个线程执行,我们可以理解为插队。当两个线程都建立完毕之后,使用join函数的线程将优先执行,而其他线程则需要它结束之后才可以,我们一般不会使用这个,因为容易造成阻塞。

Snipaste_2020-04-01_17-42-18.png


        Runnable r1 = () -> {
            for (int i = 0; i < 20; i++) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("平民用户");
                Thread.yield();
            }

        };
        Runnable r2 = () -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for (int i = 0; i < 20; i++) {
                System.out.println("至尊vip");
            }
        };
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r2);
        t1.start();
        t2.start();
        t2.join();

线程优先级

在前面我们说了,线程启动顺序实际上都是按照CPU进行调度的,是不能手动进行设置的。但是Java在CPU调度之前增加了一个调度器,以此为设置线程启动优先级。不过这个嘛..要看脸... 引入CPU很可能会优先运行你设置优先级较低的线程。
使用setPriority()函数进行设置优先级(1-10),getPriority()查看优先级。
如果不设置,默认优先级为5.

        priorityDemo p1 = new priorityDemo();
        priorityDemo p2 = new priorityDemo();
        priorityDemo p3 = new priorityDemo();
        priorityDemo p4 = new priorityDemo();

        Thread t1 = new Thread(p1, "p1");
        Thread t2 = new Thread(p2, "p2");
        Thread t3 = new Thread(p3, "p3");
        Thread t4 = new Thread(p4, "p4");

        t1.setPriority(Thread.MIN_PRIORITY);
        t1.start();

        t2.setPriority(6);
        t2.start();

        t3.setPriority(7);
        t3.start();

        t4.setPriority(Thread.MAX_PRIORITY);  //内置的常量,很显然,MAX是10,MIN是1.
        t4.start();