java实习生面试(多线程)

1、协程和线程和进程的区别?

进程: 进程是具有一定独立功能的程序,进程是系统资源分配和调度的最小单位。每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信。由于进程比较重量,占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全。

线程: 线程是进程的一个实体,线程是内核态,而且是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据。

协程: 又称为微线程,是一种用户态的轻量级线程,协程不像线程和进程需要进行系统内核上的上下文切换,协程的上下文切换是由用户自己决定的,有自己的上下文,所以说是轻量级的线程,也称之为用户级别的线程就叫协程,一个线程可以多个协程,线程进程都是同步机制,而协程则是异步 。Java的原生语法中并没有实现协程,目前python、Lua和GO等语言支持

2、请问java实现多线程有哪几种方式,有什么不同,比较常用哪种?

2.1 继承Thread

继承 Thread,重写里面run()方法,创建实例,执行start()方法。
优点:代码编写最简单直接操作。
缺点:没返回值,继承一个类后,没法继承其他的类,拓展性差。

public class ThreadDemo1 extends Thread {
    @Override
    public void run() {
        System.out.println("继承Thread实现多线程,名称:"+Thread.currentThread().getName());
    }
}

public static void main(String[] args) {
      ThreadDemo1 threadDemo1 = new ThreadDemo1();
      threadDemo1.setName("demo1");
      // 执行start
      threadDemo1.start();
      System.out.println("主线程名称:"+Thread.currentThread().getName());
}

2.2 实现Runnable接口

自定义类实现 Runnable,实现里面run()方法,创建 Thread 类,使用 Runnable 接口的实现对象作为参数传递给Thread 对象,调用strat()方法。
优点:线程类可以实现多个几接口,可以再继承一个类。
缺点:没返回值,不能直接启动,需要通过构造一个 Thread 实例传递进去启动。

public class ThreadDemo2 implements Runnable {
    @Override
    public void run() {
        System.out.println("通过Runnable实现多线程,名称:"+Thread.currentThread().getName());
    }
}

public static void main(String[] args) {
        ThreadDemo2 threadDemo2 = new ThreadDemo2();
        Thread thread = new Thread(threadDemo2);
        thread.setName("demo2");
    	// start线程执行
        thread.start();
        System.out.println("主线程名称:"+Thread.currentThread().getName());
}

// JDK8之后采用lambda表达式
public static void main(String[] args) {
    Thread thread = new Thread(() -> {
        System.out.println("通过Runnable实现多线程,名称:"+Thread.currentThread().getName());
    });
    thread.setName("demo2");
    // start线程执行
    thread.start();
    System.out.println("主线程名称:"+Thread.currentThread().getName());
}

2.3 实现Callable接口

创建 Callable 接口的实现类,并实现call()方法,结合 FutureTask 类包装 Callable 对象,实现多线程。
优点:有返回值,拓展性也高
缺点:Jdk5以后才支持,需要重写call()方法,结合多个类比如 FutureTask 和 Thread 类

public class MyTask implements Callable<Object> {
    @Override
    public Object call() throws Exception {
        System.out.println("通过Callable实现多线程,名称:"+Thread.currentThread().getName());
        return "这是返回值";
    }
}

public static void main(String[] args) {
    	// JDK1.8 lambda表达式
        FutureTask<Object> futureTask = new FutureTask<>(() -> {
          System.out.println("通过Callable实现多线程,名称:" +
                        			Thread.currentThread().getName());
            return "这是返回值";
        });

     	// MyTask myTask = new MyTask();
		// FutureTask<Object> futureTask = new FutureTask<>(myTask);
        // FutureTask继承了Runnable,可以放在Thread中启动执行
        Thread thread = new Thread(futureTask);
        thread.setName("demo3");
    	// start线程执行
        thread.start();
        System.out.println("主线程名称:"+Thread.currentThread().getName());
        try {
            // 获取返回值
            System.out.println(futureTask.get());
        } catch (InterruptedException e) {
            // 阻塞等待中被中断,则抛出
            e.printStackTrace();
        } catch (ExecutionException e) {
            // 执行过程发送异常被抛出
            e.printStackTrace();
        }
}

2.4 通过线程池创建线程

自定义 Runnable 接口,实现 run()方法,创建线程池,调用执行方法并传入对象。
优点:安全高性能,复用线程。
缺点: Jdk5后才支持,需要结合 Runnable 进行使用。

public class ThreadDemo4 implements Runnable {
    @Override
    public void run() {
        System.out.println("通过线程池+runnable实现多线程,名称:" +
                           Thread.currentThread().getName());
    }
}

public static void main(String[] args) {
    	// 创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for(int i=0;i<10;i++){
            // 线程池执行线程任务
            executorService.execute(new ThreadDemo4());
        }
        System.out.println("主线程名称:"+Thread.currentThread().getName());
        // 关闭线程池
        executorService.shutdown();
}

一般常用的 Runnable 和 第 四种线程池 + Runnable,简单方便扩展,和高性能 (池化的思想)。

或者说:

Java中创建线程的方式有4种,分别是

(1)写一个类继承子Thread类,重写run方法

(2)写一个类重写Runable接口,重写run方法

(3)写一个类重写Callable接口,重写call方法

(4)使用线程池

2.5 Runable Callable Thread 三者区别?

Thread 是一个抽象类,只能被继承,而 Runable、Callable 是接口,需要实现接口中的方法。
继承 Thread 重写run()方法,实现Runable接口需要实现run()方法,而Callable是需要实现call()方法。
Thread 和 Runable 没有返回值,Callable 有返回值。
实现 Runable 接口的类不能直接调用start()方法,需要 new 一个 Thread 并发该实现类放入 Thread,再通过新建的 Thread 实例来调用start()方法。
实现 Callable 接口的类需要借助 FutureTask (将该实现类放入其中),再将 FutureTask 实例放入 Thread,再通过新建的 Thread 实例来调用start()方法。获取返回值只需要借助 FutureTask 实例调用get()方法即可!

3、请你说一下线程的几个状态(生命周期)?

线程通常有五种状态,新建、就绪、运行、阻塞和死亡状态:

  • 新建状态(New):线程刚被创建,但尚未启动。如:Thread t = new Thread();
  • 就绪状态(Runnable):当调用线程对象的start()方法后,线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行。
  • 运行状态(Running):当 CPU 开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。
  • 阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
    1、等待阻塞 :运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤 醒,wait()是 Object 类的方法。
    2、同步阻塞:当线程处于运行状态时,试图获得某个对象的同步锁时,如果该对象的同步锁已经被其他线程占用,Java虚拟机就会把这个线程放到这个对象的锁池中。
    2、其他阻塞状态:当前线程执行了sleep()方法,或者调用了其他线程的join()方法,或者发出了 I/O 请求时,就会进入这个状态。线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者 I/O处理完毕时,线程重新转入就绪状态。
  • 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

4、并发的三大特性

  • 原子性
    原子性是指在一个操作中cpu不可以在中途暂停然后再调度,即不被中断操作,要不全部执行完成,要不都不执行。
    解决核心思想:把一个方法或者代码块看做一个整体,保证是一个不可分割的整体!
    就好比转账,从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。2个操作必须全部完成。
  • 有序性
    虚拟机在进行代码编译时,对于那些改变顺序之后不会对最终结果造成影响的代码,虚拟机不一定会按照我们写的代码的顺序来执行,有可能将他们重排序。实际上,对于有些代码进行重排序之后,虽然对变量的值没有造成影响,但有可能会出现线程安全问题。
  • 可见性
    一个线程A对共享变量的修改,另一个线程B能够立刻看到!

5、并发、并行、串行的区别

串行在时间上不可能发生重叠,前一个任务没搞定,下一个任务就只能等着
并行在时间上是重叠的,两个任务在同一时刻互不干扰的同时执行。
并发允许两个任务彼此干扰。统一时间点、只有一个任务运行,交替执行

6、为什么用线程池?解释下线程池参数?

原因:
1、降低资源消耗;提高线程利用率,降低创建和销毁线程的消耗。
2、提高响应速度;任务来了,直接有线程可用可执行,而不是先创建线程,再执行。
3、提高线程的可管理性;线程是稀缺资源,使用线程池可以统一分配调优监控。
线程池中的七大参数如下:
1)corePoolSize:线程池中的常驻核心线程数。

(2)maximumPoolSize:线程池能够容纳同时执行的最大线程数,此值大于等于1。

(3)keepAliveTime:多余的空闲线程存活时间,当空间时间达到keepAliveTime值时,多余的线程会被销毁直到只剩下corePoolSize个线程为止。

(4)unit:keepAliveTime的单位。

(5)workQueue:任务队列,被提交但尚未被执行的任务。

(6)threadFactory:表示生成线程池中工作线程的线程工厂,用户创建新线程,一般用默认即可。

(7)handler:拒绝策略,表示当线程队列满了并且工作线程大于等于线程池的最大显示数(maxnumPoolSize)时如何来拒绝请求执行的runnable的策略。
流程分析:

  • 线程池中线程数小于corePoolSize时,新任务将创建一个新线程执行任务,不论此时线程池中存在空闲线程;
  • 线程池中线程数达到corePoolSize时,新任务将被放入workQueue中,等待线程池中任务调度执行;
  • 当workQueue已满,且maximumPoolSize>corePoolSize时,新任务会创建新线程执行任务;
  • 当workQueue已满,且提交任务数超过maximumPoolSize,任务由RejectedExecutionHandler处理;
  • 当线程池中线程数超过corePoolSize,且超过这部分的空闲时间达到keepAliveTime时,回收该线程;
  • 如果设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize范围内的线程空闲时间达到keepAliveTime也将回收;

系统默认的拒绝策略有以下几种:

  • AbortPolicy:为线程池默认的拒绝策略,该策略直接抛异常处理。
  • DiscardPolicy:直接抛弃不处理。
  • DiscardOldestPolicy:丢弃队列中最老的任务。
  • CallerRunsPolicy:将任务分配给当前执行execute方法线程来处理。

7、线程池中线程复用原理

线程池将线程和任务进行解耦,线程是线程,任务是任务,摆脱了之前通过 Thread 创建线程时的一个线程必须对应一个任务的限制。
在线程池中,同一个线程可以从阻塞队列中不断获取新任务来执行,其核心原理在于线程池对Thread 进行了封装,并不是每次执行任务都会调用 Thread.start() 来创建新线程,而是让每个线程去执行一个“循环任务”,在这个“循环任务”中不停检查是否有任务需要被执行,如果有则直接执行,也就是调用任务中的 run 方法,将 run 方法当成一个普通的方法执行,通过这种方式只使用固定的线程就将所有任务的 run 方法串联起来。

8、线程的生命周期?线程有几种状态

1.线程通常有五种状态,创建,就绪,运行、阻塞和死亡状态。
2.阻塞的情况又分为三种:
(1)、等待阻塞:运行的线程执行wait方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待
池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify或notifyAll方法才能被唤
醒,wait是object类的方法
(2)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放
入“锁池”中。
(3)、其他阻塞:运行的线程执行sleep或join方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状
态。当sleep状态超时、join等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
sleep是Thread类的方法
1.新建状态(New):新创建了一个线程对象。
2.就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start方法。该状态的线程位于
可运行线程池中,变得可运行,等待获取CPU的使用权。
3.运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4.阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进
入就绪状态,才有机会转到运行状态。
5.死亡状态(Dead):线程执行完了或者因异常退出了run方法,该线程结束生命周期。

9、请你说一下线程状态转换相关方法:sleep/yield/join wait/notify/notifyAll 的区别?

  • Thread下的方法:
    sleep():属于线程 Thread 的方法,让线程暂缓执行,等待预计时间之后再恢复,交出CPU使用权,不会释放锁,抱着锁睡觉!进入超时等待状态TIME_WAITGING,睡眠结束变为就绪Runnable
    yield():属于线程 Thread 的方法,暂停当前线程的对象,去执行其他线程,交出CPU使用权,不会释放锁,和sleep()类似,让相同优先级的线程轮流执行,但是不保证一定轮流,
    注意:不会让线程进入阻塞状态 BLOCKED,直接变为就绪 Runnable,只需要重新获得CPU使用权。
    join():属于线程 Thread 的方法,在主线程上运行调用该方法,会让主线程休眠,不会释放锁,让调用join()方法的线程先执行完毕,再执行其他线程。类似让救护车警车优先通过!!
  • Object下的方法:
    wait():属于 Object 的方法,当前线程调用对象的 wait()方法,会释放锁,进入线程的等待队列,需要依靠notify()或者notifyAll()唤醒,或者wait(timeout)时间自动唤醒。
    notify():属于 Object 的方法,唤醒在对象监视器上等待的单个线程,随机唤醒。
    notifyAll():属于Object 的方法,唤醒在对象监视器上等待的全部线程,全部唤醒

10、volatile关键字(了解)

保证了线程间的可见性:
volatile关键字为域变量的访问提供了一种免锁机制,使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新,因此每次使用该域就要重新计算,而不是使用寄存器中的值。需要注意的是,volatile不会提供任何原子操作,它也不能用来修饰final类型的变量。

和synchronize的区别:
这俩用途不一样吧,volatile只能保证不重排序和可见性,并不能保证并发顺序,synchronize能拿到锁,volatile没有

11、ThreaLocal知道吗?

Java中每一个线程都有自己的专属本地变量, JDK 中提供的ThreadLocal类,ThreadLocal类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。

12、用它可能会带来什么问题?

如果在线程池中使用ThreadLocal会造成内存泄漏,因为当ThreadLocal对象使用完之后,应该要把设置的key,value,也就是Entry对象进行回收,但线程池中的线程不会回收,而线程对象是通过强引用指向ThreadLocalmap,ThreadLocalmap也是通过强引用指向Entry对象,线程不被回收,Entry对象也就不会被回收,从而出现内存泄漏,解决办法是:在使用了ThreadLocal对象之后,手动调用ThreadLocal的remove方法,手动清除Entry对象。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Java实习生面试的范围通常涵盖以下几个方面: 1. Java基础知识:包括Java语言特性、数据型、面向对象编程、集合框架、异常处理、多线程等。 2. Web开发技术:如HTML、CSS、JavaScript、jQuery、Ajax、Servlet、JSP等。 3. 数据库技术:如MySQL、Oracle等关系型数据库,以及NoSQL数据库。 4. 框架和工具:如Spring、Hibernate、MyBatis、Maven等。 5. 网络编程:如TCP/IP、HTTP、HTTPS等协议的基本原理和应用。 6. 其他方面:如算法、数据结构、操作系统、设计模式等。 当然,具体的面试范围还要根据招聘公司的实际需求而定。建议在面试前了解公司业务和所用技术,有针对性地准备相关知识。 ### 回答2: java实习生面试的范围通常包括以下几个方面: 1. Java基础知识:面试官会考察实习生Java语言的了解程度,包括Java的核心概念、面向对象编程思想、基本语法、常用库等。例如,对于实习生,可能会问到字符串操作、数组、集合、异常处理等基础内容。 2. 数据结构和算法: 实习生需要掌握常用的数据结构如栈、队列、链表、二叉树等,并能够熟练运用常见算法如排序、查找、递归、动态规划等解决问题。 3. 数据库知识:实习生需要了解数据库的基本概念和操作,如SQL语句的编写、数据库事务、索引、关系模型等。 4. Web开发:对于应用开发方向的实习生,需要了解Java Web相关的技术,如Servlet、JSP、Spring、MVC等,以及常见的前端技术如HTML、CSS、JavaScript等。 5. 操作系统和网络:了解操作系统的基本原理和常见的网络通信协议,如TCP/IP协议栈、HTTP、FTP等,对多线程编程和网络编程有一定了解。 6. 编码规范和设计模式:实习生需要了解常见的编码规范,如命名规范、代码风格约定等,并且对于常见的设计模式如单例模式、工厂模式、观察者模式等有一定的理解。 除了以上内容,实习生面试中也可能会问到一些项目经验、沟通能力、团队协作等方面的问题,以全面考察实习生的能力和潜力。此外,每个公司面试的重点可能会有所不同,因此在准备面试时,还需要研究目标公司的业务特点和技术要求,以便更好地针对性准备。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值