• 并发编程快速入门


    1、线程与进程的区别

    进程是所有线程的集合,每一个线程是进程中的一条执行路径。

    比方:通过查看 windows 任务管理器中的列表,我们可以把运行在内存中的 exe 文件理解成进程,进程是受操作系统管理的基本运行单元。

    2、为什么要使用多线程?

    主要体现在多线程提高程序效率,但是需要注意,并不是使用了多线程就一定能提升性能,有的情况反而会降低性能。

    多线程应用场景:

    2.1、避免阻塞

    我们知道,在我们单线程中,代码是顺序执行的,如果前面的操作发生了阻塞,那么就会影响到后面的操作,这时候可以采用多线程,可以理解成异步调用;其实前端里的 ajax 就是一个很好地例子,默认 ajax 是开启异步的,调用时浏览器会启一个新的线程,不阻塞当前页面的正常操作;

    2.2、避免CPU空转

    以http server为例,如果只用单线程响应HTTP请求,即处理完一条请求,再处理下一条请求的话,CPU会存在大量的闲置时间;

    因为处理一条请求,经常涉及到RPC、数据库访问、磁盘IO等操作,这些操作的速度比CPU慢很多,而在等待这些响应的时候,CPU却不能去处理新的请求,因此http server的性能就很差;

    所以很多web容器,都采用对每个请求创建新线程来响应的方式实现,这样在等待请求A的IO操作的等待时间里,就可以去继续处理请求B,对并发的响应性就好了很多 。

    3、多线程常见的两种创建方式

    3.1、继承Thread类,重写run方法
    /**
     * author:  niceyoo
     * blog:    https://cnblogs.com/niceyoo
     * desc:
     */

    public class ThreadDemo {
        public static void main(String[] args{
            System.out.println("-----多线程创建开始-----");
            /* 1.创建一个线程*/
            CreateThread createThread = new CreateThread();
            /* 2.开始执行线程 注意 开启线程不是调用run方法,而是start方法*/
            System.out.println("-----多线程创建启动-----");
            createThread.start();
            System.out.println("-----多线程创建结束-----");
        }
    }

    class CreateThread extends Thread {

        /*run方法中编写 多线程需要执行的代码*/
        @Override
        public void run() 
    {
            for (int i = 0; i< 10; i++) {
                System.out.println("i:" + i);
            }
        }
    }

    打印结果:

    -----多线程创建开始-----
    -----多线程创建启动-----
    -----多线程创建结束-----
    i:0
    i:1
    i:2
    i:3
    i:4
    i:5
    i:6
    i:7
    i:8
    i:9

    线程调用 start() 方法后,代码并没有从上往下执行,而是有一条新的执行分支。

    注意:画图演示多线程不同执行路径。

    3.2、实现Runnable接口,重写run方法
    /**
     * author:  niceyoo
     * blog:    https://cnblogs.com/niceyoo
     * desc:
     */

    class CreateRunnable implements Runnable {

        @Override
        public void run() 
    {
            for (int i = 0; i< 10; i++) {
                System.out.println("i:" + i);
            }
        }
    }

    public class ThreadDemo2 {
        public static void main(String[] args{
            System.out.println("-----多线程创建开始-----");
            /* 1.创建一个线程 */
            CreateRunnable createThread = new CreateRunnable();
            /* 2.开始执行线程 注意 开启线程不是调用run方法,而是start方法 */
            System.out.println("-----多线程创建启动-----");
            Thread thread = new Thread(createThread);
            thread.start();
            System.out.println("-----多线程创建结束-----");
        }
    }

    打印结果:

    -----多线程创建开始-----
    -----多线程创建启动-----
    -----多线程创建结束-----
    i:0
    i:1
    i:2
    i:3
    i:4
    i:5
    i:6
    i:7
    i:8
    i:9
    使用继承Thread类还是使用实现Runnable接口好?

    使用实现Runnable接口好,继承方式的扩展性不强,java总只支持单继承,如果一个类继承Thread就不能继承其他的类了。

    4、守护线程

    java 中有两种线程,一种是用户线程,一种是守护线程。

    • 用户线程:指用户自定义创建的线程,主线程停止,用户线程不会停止。

    • 守护线程:当前进程不存在或主线程停止,守护进程也会被停止。

    如何使用守护线程?

    只需要调用 setDaemon(true) 方法即可设置为守护线程。

    /**
     * author:  niceyoo
     * blog:    https://cnblogs.com/niceyoo
     * desc:
     */

    public class DaemonThread {
        public static void main(String[] args{
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() 
    {
                    while (true) {
                        try {
                            Thread.sleep(100);
                        } catch (Exception e) {
                        }
                        System.out.println("我是子线程...");
                    }
                }
            });
            thread.setDaemon(true);
            thread.start();
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(100);
                } catch (Exception e) {

                }
                System.out.println("我是主线程");
            }
            System.out.println("主线程执行完毕!");
        }
    }

    运行结果:

    ...
    我是主线程
    我是子线程...
    我是主线程
    主线程执行完毕!

    从运行结果看到,main函数执行完了,守护线程也跟着停止了。

    5、多线程运行状态

    线程从创建、运行到结束,总是处于下面五个状态之一:

    新建状态、就绪状态、运行状态、阻塞状态以及死亡状态。

    5.1、新建状态

    当用new操作符创建一个线程时, 例如new Thread(r),线程还没有开始运行,此时线程处在新建状态。 当一个线程处于新生状态时,程序还没有开始运行线程中的代码

    5.2、就绪状态

    一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。

    处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序(thread scheduler)来调度的。

    5.3、运行状态

    当线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法.

    5.4、阻塞状态

    线程运行过程中,可能由于各种原因进入阻塞状态:

    1. 线程通过调用sleep方法进入睡眠状态;
    2. 线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者;
    3. 线程试图得到一个锁,而该锁正被其他线程持有;
    4. 线程在等待某个触发条件;
    5.5、死亡状态

    有两个原因会导致线程死亡:

    1. run方法正常退出而自然死亡,
    2. 一个未捕获的异常终止了run方法而使线程猝死。

    为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用 isAlive() 方法。如果是可运行或被阻塞,这个方法返回true; 如果线程仍旧是new状态且不是可运行的, 或者线程死亡了,则返回false.

    6、join()方法的作用

    在多线程中也是有执行的优先级的,所谓的优先级,就是cpu是否格外关注这位小兄弟,优先级越大,自然获得的好处就越多。

    当在主线程当中执行到 小弟.join() 方法时,就认为主线程应该把执行权让给 小弟。

    举一个例子:

    创建一个线程,如何让子线程执行完毕后,主线程才能执行呢?

    public class ThreadDemo3 {

        public static void main(String[] args) throws InterruptedException {
            Thread t1 = new Thread(new Runnable() {

                @Override
                public void run() 
    {
                    for (int i = 0; i < 10; i++) {
                        try {
                            Thread.sleep(10);
                        } catch (Exception e) {

                        }
                        System.out.println(Thread.currentThread().getName() + "i:" + i);
                    }
                }
            });
            t1.start();
            /* 当在主线程当中执行到t1.join()方法时,就认为主线程应该把执行权让给t1 */
            t1.join();
            for (int i = 0; i < 10; i++) {
                try {
                    Thread.sleep(10);
                } catch (Exception e) {

                }
                System.out.println("main" + "i:" + i);
            }
        }
    }

    打印结果:

    Thread-0i:0
    Thread-0i:1
    Thread-0i:2
    Thread-0i:3
    Thread-0i:4
    Thread-0i:5
    Thread-0i:6
    Thread-0i:7
    Thread-0i:8
    Thread-0i:9
    maini:0
    maini:1
    maini:2
    maini:3
    maini:4
    maini:5
    maini:6
    maini:7
    maini:8
    maini:9

    7、优先级

    虽然上边在介绍 join 方法时提到了优先级,但是在使用 join() 方法后,该线程却变成了完全主导,这或许并不是你想要的结果。

    现代操作系统基本采用时分的形式调度运行的线程,线程分配得到的时间片的多少决定了线程使用处理器资源的多少,也对应了线程优先级这个概念。在JAVA线程中,通过一个int priority来控制优先级,范围为1-10,其中10最高,默认值为5。下面是源码(基于1.8)中关于priority的一些量和方法。

    class PrioritytThread implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().toString() + "---i:" + i);
            }
        }
    }

    public class ThreadDemo4 {
        public static void main(String[] args) {
            PrioritytThread prioritytThread = new PrioritytThread();
            Thread t1 = new Thread(prioritytThread);
            Thread t2 = new Thread(prioritytThread);
            t1.start();
            /* 注意设置了优先级, 不代表每次都一定会被执行。 只是CPU调度会有限分配 */
            t1.setPriority(10);
            t2.start();
        }
    }

    打印结果:

    Thread[t1,10,main]---i:0
    Thread[t1,10,main]---i:1
    Thread[t1,10,main]---i:2
    Thread[t1,10,main]---i:3
    Thread[t1,10,main]---i:4
    Thread[t1,10,main]---i:5
    Thread[t1,10,main]---i:6
    Thread[t1,10,main]---i:7
    Thread[t1,10,main]---i:8
    Thread[t1,10,main]---i:9
    Thread[t2,5,main]---i:0
    Thread[t2,5,main]---i:1
    Thread[t2,5,main]---i:2
    Thread[t2,5,main]---i:3
    Thread[t2,5,main]---i:4
    Thread[t2,5,main]---i:5
    Thread[t2,5,main]---i:6
    Thread[t2,5,main]---i:7
    Thread[t2,5,main]---i:8
    Thread[t2,5,main]---i:9

    7、常见的面试题

    进程与线程的区别?

    答:进程是所有线程的集合,每一个线程是进程中的一条执行路径。

    为什么要用多线程?

    答:提高程序效率

    多线程创建方式?

    答:继承Thread或Runnable 接口。

    使用继承Thread类还是使用实现Runnable接口好?

    答:实现Runnable接口好,继承方式的扩展性不强,java总只支持单继承,如果一个类继承Thread就不能继承其他的类了。

    你在哪里用到了多线程?

    答:主要能体现到多线程提高程序效率。

    举例:分批发送短信。

    8、最后总结

    我们了解了什么是线程,线程是一条执行路径,每个线程互不影响;

    了解了什么是多线程,多线程在一个线程中,有多条不同的执行路径,并行执行,目的为了提高程序效率。

    了解了线程创建常见的两种方式:继承Thread类实现run方法,或者实现Runnable接口。

    事实上,实际开发中这两种方式并不常见,而是使用线程池来进行管理。

    了解了线程的几种状态,新建、就绪、运行、阻塞、死亡。

    了解了线程里面也是有优先级的,用数值1-10来记录,默认是5,最大是10,通过调用 setPriority(10) 来设置优先级,需要一提的是,并不是优先级越大就一定要先执行完,只是优先执行完的概率要大。

    我创建了一个java相关的公众号,用来记录自己的学习之路,感兴趣的小伙伴可以关注一下微信公众号哈:niceyoo

  • 相关阅读:
    java&nbsp;split
    百度知道
    2014年10月27日
    2014年10月27日
    mybatis批量update,返回行数为-1
    mybatis批量插入:oracle和mysql的区别
    oracle数据库,mybatis批量insert,缺失values字段
    java后台接收json数据,报错com.alibaba.fastjson.JSONObject cannot be cast to xxx
    C++——运算符重载(上)
    C++——友元
  • 原文地址:https://www.cnblogs.com/niceyoo/p/11154870.html
Copyright © 2020-2023  润新知