每日六道java新手入门面试题,通往自由的道路--多线程

目录
  • 1. 你可以讲下进程与线程的区别?为什么要用多线程?
  • 2. 什么是上下文切换?
  • 3. 说说你知道的几种创建线程的方式
  • 4. 昨天你讲到创建线程后使用start方法去调用线程,为什么run方法不行呢?有什么区别?
  • 5. 你知道你开启一个线程后,它的状态有那些吗?
  • 6. 既然讲到超时方法,那你讲下sleep和wait的区别和他们需要怎样唤醒
  • 总结:

1. 你可以讲下进程与线程的区别?为什么要用多线程?

  • 进程:进程是程序的一次执行过程,是系统运行程序的基本单位。
  • 线程:单个进程中执行中每个任务就是一个线程。线程是进程中执行运算的最小单位。
  • 区别
    • 一个程序至少有一个进程,一个进程至少有一个线程。
    • 一个线程只能属于一个进程,但是一个进程可以拥有多个线程。多线程处理就是允许一个进程中在同一时刻执行多个任务即多个线程。
    • 每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行

为什么要用多线程:

  • 发挥多核CPU的优势,采用多线程的方式去同时完成几件事情而不互相干扰。
  • 能够有效的防止阻塞,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,也不会影响其它任务的执行。
  • 提高程序的效率。

2. 什么是上下文切换?

上下文切换一般发生在多线程情况下,因为一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片并轮转的形式。而在多核cpu下,多线程是并行工作的,如果线程数多,单个核又会并发的调度线程,运行时就会让一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于上下文切换。

对于我们Java程序线程来说,一旦一个线程抢占到CPU资源的使用权后,另一个线程需要保存当前的一个状态,以便下次抢占成功后可以回到当前状态,JVM中有块内存地址叫程序计数器,用于记录保存线程执行到哪一行代码,它是每个线程独有的。执行任务从保存到再次加载的过程就是上下文切换。

实际上,上下文切换也是对系统意味着来说会消耗大量的CPU时间,消耗大量资源。

以下几种情况会发生上下文切换。

  • 线程的cpu时间片用完
  • 在发生垃圾回收的时候
  • 我们自己调用了 sleep、yield、wait、join、synchronized、lock 等方法

3. 说说你知道的几种创建线程的方式

创建线程有以下方式:

继承Thread类,重载它的run方法。

  • 在我们自己定义一个继承于Thread类的子类,并重写里面run方法,编写相关逻辑代码。
  • 在测试类中创建我刚自定义的线程子类对象
  • 调用子类实例的star方法来启动线程,通过start方法去调用到run方法里面的逻辑。

实现 Runnalbe接口,重载 Runnalbe接口中的run方法实现 。

  • 我们定义一个实现Runnable接口实现类,并重写里面的run方法
  • 在测试类中创建一个我们刚定义的接口实现类的实例,以实例对象作为target创建Thead对象,而得到的Thread对象就是我们线程子类对象。
  • 最后调用线程对象的start方法

实现Callable接口方式,重写Callable接口中的call方法,并且这个call方法可以有返回值。

  • 我们定义一个实现创建实现Callable接口实现类,并重写里面的call方法,注意它是call方法,并且有返回值。
  • 在测试类中创建一个我们刚定义的接口实现类的实例,以实例对象为参数创建FutureTask对象,并把创建出来FutureTask对象作为参数去创建Thread对象,而得到的Thread对象就是我们线程子类对象。
  • 最好调用线程对象的start方法。

需要注意三者的区别:

  • Thread是继承,而Runnalbe、Callable是实现。对于继承来说,只能单继承,而接口可以多实现。如果继承了 Thread类就无法再继承其他类了。
  • 三者都是最后采用Thread.start()去启动线程,而不是调用run方法,或者call方法的。
  • Runnable接口 run 方法无返回值;Callable接口 call 方法有返回值。
  • Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call 方法允许抛出异常,可以获取异常信息
  • 使用实现 Runnable接口的方式创建的线程可以处理同一资源,而实现资源的共享,还可以继承其他类。

4. 昨天你讲到创建线程后使用start方法去调用线程,为什么run方法不行呢?有什么区别?

我们先来看看代码吧。

public class ThreadDemo {

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        MyThread myThead2 = new MyThread();
//        myThread.start();
//        myThead2.start();
        myThread.run();
        myThead2.run();
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 6; i++) {
            System.out.println(Thread.currentThread().getName() + " :" + i);
            try {
                sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

这里我们创建了MyThread继承了Thread类,这种方法是一种可以创建线程的方式。接着我们在main方法中创建了两个线程,都调用了start方法和run方法。让我们先看看结果吧!

// 注释掉两个run方法 开启start方法得到的结果
Thread-0 :0
Thread-1 :0
Thread-1 :1
Thread-0 :1
Thread-1 :2
Thread-0 :2
Thread-1 :3
Thread-0 :3
Thread-1 :4
Thread-0 :4
Thread-1 :5
Thread-0 :5

// 注释掉两个start方法 开启run方法得到的结果
main :0
main :1
main :2
main :3
main :4
main :5
main :0
main :1
main :2
main :3
main :4
main :5

接下来我们讲一下:

1.start方法的作用:

启动线程,相当于开启一个线程调用我们重写的run方法里面的逻辑,此时相当于有两个线程,一个main的主线程和开启的子线程。可以看到我们的代码,相当于有三个线程,一个主线程、一个Thread-0线程和一个Thread-1线程。并且线程之间是没有顺序的,他们是抢占cpu的资源来回切换的。

2.run方法的作用:

执行线程的运行时代码,相当于我们只是单纯的调用一个普通方法。然后通过主线程的顺序调用的方式,从myThread调用run方法结束后到myThread2去调用run方法结束,并且我们也可以看到我们控制台中的线程名字就是main主线程。

3.run方法我们可以重复调用,而start方法在一个线程中只能调用一次。即myThread这个实例对象只能调用一次start方法,如果再调用一次start方法的话,就会抛出IllegalThreadStateException 的异常。

4.我们调用start方法算是真正意义上的多线程,因为它是额外开启一个子线程去调用我们的run方法了。如果我们是调用run方法,就需要等待上一次的run方法执行完毕才能调用下一次。所以我们要调用start方法充分挥多核CPU的优势,采用多线程的方式去同时完成几件事情而不互相干扰。

5. 你知道你开启一个线程后,它的状态有那些吗?

我们可以通过查看Thread的源码中State枚举发现有6个状态:

    public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
        NEW,

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
        RUNNABLE,

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
        BLOCKED,

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called <tt>Object.wait()</tt>
         * on an object is waiting for another thread to call
         * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
         * that object. A thread that has called <tt>Thread.join()</tt>
         * is waiting for a specified thread to terminate.
         */
        WAITING,

        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
        TIMED_WAITING,

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        TERMINATED;
    }

接下来我们具体来说说吧:

NEW(新建)

线程刚被创建,还只是一个实例对象,并未调用start方法启动。。MyThread myThread = new MyThread只有线程对象,没有线程特征。

Runnable(可运行)

在创建对象对象完成后,调用了myThread.start()方法线程,可以在Java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。也可以叫做处于就绪状态,需要等待被线程调度选中,获取cpu资源的使用权。

Teminated(被终止)

因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。代表着此线程的生命周期结束了。

处于运行状态中的线程由于某种原因,暂时放弃对 CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被 CPU 调用以进入到运行状态。有以下三种相关阻塞状态:

Blocked(锁阻塞)

当一个线程试图获取一个对象锁如(Synchronzied或Lock),而该对象锁被其他的线程持有,则该线程进入Blocked状态;只有当该线程持有锁时,该线程将变成Runnable状态。

Waiting(无限等待)

在调用了wait方法,JVM会把该线程放入等待队列中,等待另一个线程执行一个(唤醒),该线程此时状态表示进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。

TimedWaiting(计时等待)

同waiting状态一样,调用sleep方法或者其他超时方法时,他们将进入Timed Waiting状态。不过这一状态只需保持到超时期满或者接收到唤醒通知。

6. 既然讲到超时方法,那你讲下sleep和wait的区别和他们需要怎样唤醒

sleep和wait方法他们都是可以暂停当前线程的执行,进入一个阻塞状态。

sleep:

我们可以指定睡眠时间,即让程序暂停指定时间运行,时间到了会继续执行代码,如果时间未到我们想要换醒需要调用interrupt 方法来随时唤醒即可。而调用interrupt 会使得sleep()方法抛出InterruptedException 异常,当sleep()方法抛出异常我们就中断了sleep的方法,从而让程序继续运行下去。

wait:

调用该方法,可以导致线程进入等待阻塞状态,会一直等待直到它被其他线程通过notify或者notifyAll方法唤醒。或者也可以使用wait(long timeout)表示时间到了自动执行,类似于sleep(long millis)。

notify():该方法会随机选择一个在该对象上调用wait方法的线程,解除其阻塞状态。

notifyAll():该方法会唤醒所有的wait对象。

两者的区别:

  • 两者所属的类不同:sleep是 Thread线程类的静态方法;而wait是 Object类的方法。
  • 两者是否是否锁呢:sleep不释放锁;wait释放锁。
  • 两者所使用的场景:sleep可以在任何需要的场景下调用;而wait必须使用在同步代码块或者同步方法中。
  • 两者不同唤醒机制:sleep方法执行睡眠时间完成后,线程会自动苏醒;而wait方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify或者 notifyAll方法,或者可以使用wait(long timeout)超时后线程会自动苏醒。

总结:

这篇文章就到这里了,如果这篇文章对你也有所帮助,希望您能多多关注我们的更多内容!

(0)

相关推荐

  • 每日六道java新手入门面试题,通往自由的道路第二天

    目录 1. 你可以讲下你对String类有什么其他的了解吗? 2. == 和 equals 的区别 3. String s= new String("nz")创建了几个字符串对象? 4. 你可以讲下JVM的运行时数据区或者说内存结构吗? 5. 类加载过程 6. 而其中类加载器是什么,那有哪些呢? 总结 1. 你可以讲下你对String类有什么其他的了解吗? 在看String的源码过程中,可以发现String 内部实际存储结构为 char数组,在String中有几个比较重要的构造函数:

  • 每日六道java新手入门面试题,通往自由的道路

    目录 1. 面试第一步,自我介绍. 2. 请你简述下对面向对象的理解,面向对象和面向过程的区别? 3. 那你再讲下面向对象的特征有哪些方面 4. 请问:我们是否可以继承 String类? 5. 我再问下final.finally.finalize这三者有什么区别吗? 6. 请讲下String和 StringBuilder.StringBuffer之间的区别? 总结 1. 面试第一步,自我介绍. 这个自我介绍,在整个面试当中可以说是第一步,如果你能把你想说的重点说出来,把面试官带到你准备好的技术点

  • 每日六道java新手入门面试题,通往自由的道路--线程池

    目录 1. 你知道线程池吗?为什么需要使用线程池? 小结: 2. 你知道有多少种创建线程池的方式 3. 线程池的五种状态你有了解吗? 4. 你知道ThreadPoolExecutor的构造方法和参数吗 5. 你可以说下线程池的执行过程原理吗 6. 能否写一个简单线程池的demo? 总结 1. 你知道线程池吗?为什么需要使用线程池? 在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源. 而在Java中, JVM 中每创建和销毁线程就需要资源和时间的损耗了,

  • 三道java新手入门面试题,通往自由的道路--多线程

    目录 1. 你知道线程安全问题吗? 2. 那如何解决线程安全问题呢? 3. 那你讲下死锁是什么吧? 总结 1. 你知道线程安全问题吗? 线程安全问题:一般指在多线程模式下,多个线程对同一个共享数据进行操作时,第一个线程还没来得及更新共享数据,从而导致另外一个线程没得到最新的数据,并更新数据,从而产生线程安全问题.比较常见的场景有买票. 我举个例子吧: 需求:比如买周杰伦演唱会的门票,此时有三个窗口同时卖总共100张票.窗口就是线程对象,而100张票的资源,此时就相当于多个线程去抢占cpu的资源去

  • 三道java新手入门面试题,通往自由的道路--锁+Volatile

    目录 1. 你知道volatile是如何保证可见性吗? 小结: 2. 悲观锁和乐观锁可以讲下你的理解吗? 3. 你还知道什么其他的锁吗? 总结 1. 你知道volatile是如何保证可见性吗? 我们先看一组代码: public class VolatileVisibleDemo { public static boolean initFlag = false; public static void main(String[] args) { new Thread(new Runnable() {

  • 每日几道java新手入门面试题,通往自由的道路

    目录 1.请你说一下什么是面向对象? 2.请你简述一下面向对象的三个基本特征? 3.为什么说 Java 是一种半解释半编译的程序设计语言呢? 4.请你说一下Java中的8大基本类型是那些? 5.请你讲讲抽象类和接口有什么区别? 6.请判断当一个对象被当作参数传递给一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递? 7.请你说一下JVM/JRE/JDK的区别? 8.请你说一下方法重载和方法重写的区别? 9.请你说一下List接口和Set接口的区别? 1

  • 三道java新手入门面试题,通往自由的道路--JVM

    目录 1. 你知道JVM内存模型吗? 2. 你知道重排序是什么吗? 3. happens-before是什么,和as-if-serial有什么区别 总结 1. 你知道JVM内存模型吗? 在Java的并发中采用的就是JVM内存共享模型即JMM(Java Memory Model),它其实是是JVM规范中所定义的一种内存模型,跟计算机的CPU缓存内存模型类似,是基于CPU缓存内存模型来建立的,Java内存模型是标准化的,屏蔽掉了底层不同计算机的区别. 那我们先来讲下计算机的内存模型: 其实早期计算机

  • 每日六道java新手入门面试题,通往自由的道路--JVM

    目录 1. JVM是如何判断对象是否可回收 2. 你知道有什么垃圾回收的常见算法吗? 3. 你知道有什么垃圾收集器吗? 4. 那你知道什么时候才会触发Full GC 5. JVM中四种引用你有了解过吗? 6. 说说你知道的几种主要的JVM参数 1.堆设置 2.收集器设置 3.并行收集器设置 4.并发收集器设置 5.JVM 调优的参数 总结 1. JVM是如何判断对象是否可回收 垃圾收集器在做垃圾回收的时候,首先需要判断一个对象是存活状态还是死亡状态,死亡的对象将会被标识为垃圾数据并等待收集器进行

  • 每日六道java新手入门面试题,通往自由的道路--多线程

    目录 1. 你可以讲下进程与线程的区别?为什么要用多线程? 2. 什么是上下文切换? 3. 说说你知道的几种创建线程的方式 4. 昨天你讲到创建线程后使用start方法去调用线程,为什么run方法不行呢?有什么区别? 5. 你知道你开启一个线程后,它的状态有那些吗? 6. 既然讲到超时方法,那你讲下sleep和wait的区别和他们需要怎样唤醒 总结: 1. 你可以讲下进程与线程的区别?为什么要用多线程? 进程:进程是程序的一次执行过程,是系统运行程序的基本单位. 线程:单个进程中执行中每个任务就

  • 三道MySQL新手入门面试题,通往自由的道路

    目录 1. 讲讲你认识MySQL锁吧 2. 你知道什么是事务.四大特性.隔离级别吗? 3. MyISAM 和 InnoDB 存储引擎的区别 总结 1. 讲讲你认识MySQL锁吧 对于数据库来讲,读写都是非常频繁的吧,在并发量来的时候,在进行读写操作时,可能会产生数据的不一致,这时候需要一些机制来保证访问的次序,所以锁就可以在一定限度保护它的一致性. 首先我们可以按锁的粒度分: 1.表级锁:它上锁是锁住的整张表,当下一个事务来访问的时候,必须等到当前事务把锁释放了,才能对表进行操作访问. 特点:表

随机推荐