美文网首页
Java并发编程一之线程基础

Java并发编程一之线程基础

作者: echoSuny | 来源:发表于2020-05-14 00:39 被阅读0次

    CPU核心数与线程数的关系

    在计算机早期的时候一个核心对应一个线程,它们之间的关系是1:1,也就是说如果你的CPU是4核的,那么在同一时间片只能运行4个线程。后期随着技术的发展出现了超线程技术,可以使一个CPU核心对应两个线程,比列为1 : 2。

    CPU时间片轮转机制

    在还没有出现多核CPU的时候,CPU都是单核的。如果想要执行多线程,那么就采用了一种名为 RR调度 的随机算法指导CPU在线程之间高速切换。但是如果线程的数量过多的话,就会出现明显的卡顿。假设以前只有5个线程,那么理想情况下每个线程每秒中都能获得200ms的执行时间。但是现在有1000个线程同时存在(线程状态必须是可执行的),那每个线程分到的时间就只剩下1ms了。上述的也仅仅是在理想情况下,实际上并不是这样的。CPU在线程之间来回切换我们称之为 上下文切换 。每一次上下文切换的开销是很大的。假设执行一个简单的 1+1 需要消耗一个时间周期,但是每进行一次上下文切换需要消耗大约5000~20000个时间周期。假设现在要从线程A切换到线程B上,首先需要把线程A的临时数据进行保存,然后再进行切换,当下次重新切换到A线程的时候还需要把临时数据重新读取出来。所以当我们的电脑上打开的程序多了就会感觉卡顿。

    进程和线程

    进程:操作系统所管理的最小单元,一个进程至少有一个线程。如果一个线程中的线程还有一个存活,那么这个进程就还会存活。
    线程:CPU调度的最小单元

    并发和并行

    并行


    可以看到上图中的运动员在各自的跑道中在进行赛跑,这种行为就是并行。也就是说统一时刻有多个相同的动作(跑步)存在执行。
    并发
    并发更强调的是吞吐量。即在某一个时间段内执行了多少。

    按照图片来进行解释的话就是在半个小时内这个路口通过了多少车。而这个吞吐量就代表了并发能力。也就是说并发是需要有一个时间单位存在。

    Java多线程编程

    Java默认就是多线程的,这一点我们可以通过代码来进行验证:

    public static void main(String[] args) {
            // 虚拟机线程管理的接口
            ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
            // 获取所有的线程信息
            ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
            for (ThreadInfo threadInfo : threadInfos) {
                System.out.println(threadInfo.getThreadId() + "-----" + threadInfo.getThreadName());
            }
        }
    
    运行结果

    可以看到即使我们只运行了一个主线程,但是还是有其他的几个线程在执行。可以看到并没有GC线程。也就是说在程序刚开始运行的时候GC线程是不会启动的,当系统检测到有垃圾需要回收的时候才会去启动GC线程。

    启动线程的方式
    1. 继承Thread类
    public class ThreadImpl extends Thread{
            @Override
            public void run() {
                super.run();
                // do something
            }
        }
    
    // 使用方式
    ThreadImpl thread1 = new ThreadImpl();
    thread1.start();
    
    1. 实现Runnable接口
    public class ThreadImpl2 implements Runnable{
            @Override
            public void run() {
            // do something
            }
        }
    
    // 使用方式
    ThreadImpl2 runnable = new ThreadImpl2();
    Thread thread2 = new Thread(runnable);
    thread2.start();
    
    1. 实现Callable接口
    public class ThreadImpl3 implements Callable<String>{
            @Override
            public String call() throws Exception {
                return "hello";
            }
        }
    
    // 使用方式
    ThreadImpl3 callable = new ThreadImpl3();
    FutureTask<String> task = new FutureTask<>(callable);
    Thread thread3 = new Thread(task);
    thread3.start();
    // 获取返回值
    String s = task.get();
    

    需要说明一下的是这种方式其实和第二种实现Runnable是同一种方式,因为首先Thread类的构造方法中是看不见任何有关Callable的身影的,其次Callable需要使用FutureTask包装一下才能给Thread使用,而FutureTask是Runnable接口的子类,也就是说FutureTask就是一个Runnable。严格意义上来说只有两种方式新启一个线程,这在Thread源码中的注释里就已经说明了:There are two ways to create a new thread of execution.,翻译过来就是有两种方式去创建一个新的线程去执行。只不过使用Callable和FutureTask是可以允许有返回值的。Android当中的AsyncTask就是使用的这种方式。

    线程的状态

    线程的状态也叫线程的生命周期,一共有6种:

    • 初始(NEW):新创建了一个线程,但还没调用start()方法
    • 运行(RUNNABLE):Java线程中将就绪(ready)运行中(running)两种状态笼统的称为“运行”。线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法,该状态的线程位于可运行线程池中,等待被线程调度选中,获得CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变成运行中状态(running)
    • 阻塞BLOCKED:表示线程阻塞于锁
    • 等待WAITING:进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)
    • 超时等待TIMED_WAITING:该状态不同于WAITING,它可以在一定时间后自行返回
    • 终止TERMINATED:表示该线程已经执行完毕
      线程状态的变迁
      需要注意的是在Java中有且仅有一种方式能够让线程进入阻塞状态的是使用synchronized关键字。而使用Lock接口下的这种显式锁的方式不属于阻塞,因为它们的底层实现是用的上图中的LockSupport.park()的这种方式,那么使用显式锁的时候进入的是等待或者等待超时。区别就在于阻塞时被动的,而等待是主动的。也就是说当线程去拿锁的时候没有锁而不得不阻塞。而显式锁的方式是由于条件不满足而主动等待。

    常见的面试题

    1. run()和start()的区别?
    答:run()函数只是一个普通的函数,和线程没有任何关系,只不过是执行逻辑写在这里。start()则会调用native方法,最终又会回调run()函数。
    2. 如何控制线程按顺序执行?
    答:使用join()函数可以实现。下面通过例子来测试一下:

    public class Test {
    
        public static class JoinThread extends Thread {
            JoinThread(@NonNull String name) {
                super(name);
            }
    
            @Override
            public void run() {
                super.run();
                for (int i = 0; i < 20; i++) {
                    System.out.println(getName() + "-----" + i);
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            JoinThread t1 = new JoinThread("aaa");
            JoinThread t2 = new JoinThread("bbb");
            t1.start();
            t1.join();
            t2.start();
        }
    }
    

    join()函数的意思是使得当前正在执行的线程放弃执行权,并返回对应的线程。根据上述例子描述就是程序在main()函数中调用t1线程的join()方法,则main线程放弃CPU的控制权,并返回t1线程继续执行直到t1线程执行完毕,所以结果是t1线程执行完后才到主线程执行。相当于在main线程中同步t1线程,t1执行完了,main线程才有执行的机会。
    3. 在Java中能不能指定CPU去执行某个线程?
    答:不可以,Java做不到。唯一能够干预的是C语言调用内核的API去指定才行。
    4. 在项目开发中,会考虑使用Java线程的优先级吗?
    答:不会考虑。因为线程的优先级很依赖系统平台,没有办法做到对号入座。如果这样做是有风险的,不稳定。例如Java的优先级有十级,但是系统平台只有二级,这样就无法对应。
    5. sleep()和wait()的区别?
    答:首先来讲sleep()是线程的方法,而wait()属于Object的方法。其次sleep()是休眠,等休眠时间一过,线程就又会进入就绪状态,表示有资格被执行,但不一定会立即执行,因为无法得知CPU什么时候会调用。而wait()则是等待,需要别人来唤醒,被唤醒之后则和sleep()一样进入就绪状态等待CPU执行。最后sleep()可以无条件休眠,而wait()则是出于某种原因,例如条件不满足才会去等待。
    6. 在Java中能不能强制中断线程的执行?
    答:虽然提供了stop()函数,但是此函数的不推荐使用,因为太过于暴力,非常容易出现问题,很危险。建议使用interrupt()的方式来处理线程的停止。需要注意的是interrupt()只是协作的方式,不能绝对保证中断。

    public static class InterruptThread extends Thread{
            @Override
            public void run() {
                super.run();
                while (!isInterrupted()){
                    System.out.println("running ----- " + isInterrupted());
                }
                System.out.println("end -----"+ isInterrupted());
            }
        }
        public static void main(String[] args) throws InterruptedException {
            InterruptThread interruptThread = new InterruptThread();
            interruptThread.start();
            Thread.sleep(10);
            System.out.println("-----request interrupt");
            interruptThread.interrupt();
        }
    

    7. 如何让出当前线程的执行权?
    答:使用yield()函数。不过基本用不到,只在JDK的某些实现才能看到。
    8. sleep()和wait()哪个函数会清楚中断标记?
    答:sleep()函数。因为调用sleep()函数的时候需要抛出一个InterruptedException异常,那么在抛出异常的时候就会清除调用interrupt()设置的标记。

    相关文章

      网友评论

          本文标题:Java并发编程一之线程基础

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