(JAVA实习)常见面试题之多线程

引言:
Java中的多线程一直是java面试的高频热点,它的重要性不言而喻。
随着计算机的配置越来越高,我们需要将进程进一步优化,细分为线程,充分提高图形化界面的多线程的开发。

1.什么是进程和线程?

1.1 进程:进程是操作系统分配资源的基本单位,一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进程可以有多个线程,比如在Windows系统中,一个运行的xx.exe就是一个进程。它的本质是一个独立执行的程序。

1.2 线程:进程中的一个执行任务(控制单元),负责当前进程中程序的执行。一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。线程是任务调度和执行的基本单位
一个进程中可以并发多个线程,每条线程执行不同的任务,切换受系统控制。

与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。

2.线程和进程的区别是什么?

线程具有许多传统进程所具有的特征,故又称为轻型进程(Light—Weight Process)或进程元;而把传统的进程称为重型进程(Heavy—Weight Process),它相当于只有一个线程的任务。在引入了线程的操作系统中,通常一个进程都有若干个线程,至少包含一个线程。

  • 根本区别:进程是操作系统分配资源的基本单位,而线程是处理机调度和执行任务的基本单位

  • 资源开销:每个进程都有自己独立的代码和数据空间,来回的切换有巨大的开销,而线程又被称为轻量级的进程,同一类的线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。

  • 包含关系:一个进程包含多个线程,执行过程是多条线程共同完成的,线程是进程的一部分,所以线程也被称为轻量级进程

  • 内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的

  • 影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。

  • 执行过程:每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行

3.并发和并行是什么?

  • 并发(Concurrent):两个或多个事件在同一时间间隔内执行,即交替执行,多线程是并发的一种写照,一个处理器同时处理多个任务,一个处理器每次只能执行一个任务,所以并发就需要在有限的共享资源的同时,来回切换执行
  • 并行(Parallel):两个或者多个事件在同一时间同时进行,即同时做不同的事情,多核cpu,多条线程可以同时执行

4.java中创建多线程常用的有哪几种方式?

1.继承Thread

  • 方法:继承Thread之后,重写run方法,创建实例,调用start方法(有一个小问题,为什么不去直接调用run方法呢,start最后也是调用的run方法)
    因为run方法只是一个普通的方法,直接调用run不能开启线程,直接调用run方法,它会执行完方法继续往后执行,相当于是一个串行化的过程,而不是一个并发化的过程

  • 优点:代码操作简单

  • 缺点:java是单继承的,继承一个类之后没办法继承其他类,可扩展性太差

    @Override
    public void run() {
        System.out.println("多线程"+Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        ThreadTest threadTest=new ThreadTest();
        threadTest.setName("线程1");
        threadTest.start();
        System.out.println("主线程"+Thread.currentThread().getName());
    }
}

2.实现runnable接口

  • 方法:实现runnable接口,重写run方法,创建Thread类,将Runnable实现类对象传递给Thread对象,用Thread对象实例调用start方法
  • 优点:可以再继承一个类,多实现几个接口,可扩展性强
  • 缺点:不能直接调用start,需要构造一个Thread实例将参数传递进去再调用start
public class RunnableTest implements Runnable {
    @Override
    public void run() {
        System.out.println("多线程--"+Thread.currentThread().getName());
    }

    public static void main(String[] args) {
        RunnableTest runnableTest=new RunnableTest();
        Thread thread=new Thread(runnableTest);
        thread.setName("线程1");
        thread.start();
        System.out.println("主线程"+Thread.currentThread().getName());
    }
}

3.实现callable接口方式

  • 创建 Callable 接口的实现类,并实现call()方法,结合 FutureTask 类包装 Callable 对象,实现多线程。
  • 优点:有返回值,拓展性也高
  • 缺点:Jdk5以后才支持,需要重写call()方法,结合多个类比如 FutureTask 和 Thread 类
public class CallableTest implements Callable{
    @Override
    public Object call() throws Exception {
        System.out.println("线程名称:"+Thread.currentThread().getName());
        return "返回值";
        }

    public static void main(String[] args) {
        FutureTask<Object> futureTask = new FutureTask<>(() -> {
            System.out.println("线程名称:" +
                    Thread.currentThread().getName());
            return "这是返回值";
        });

        Thread thread=new Thread(futureTask);
        thread.setName("线程1");
        thread.start();
        System.out.println("主线程:"+Thread.currentThread().getName());
    }
}

4 通过线程池创建线程

  • 自定义 Runnable 接口,实现 run()方法,创建线程池,调用执行方法并传入对象。
  • 优点:安全高性能,复用线程。
  • 缺点: Jdk5后才支持,需要结合 Runnable 进行使用。
public class ThreadTest01 implements Runnable {
    @Override
    public void run() {
        System.out.println("线程池:线程名称"+Thread.currentThread().getName());
    }

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

5.Runnable、Thread、Callable三种创建多线程方法的区别?

  • Runnable和Callable是接口,可以多实现,有利于程序的可扩展性,而Thread是抽象类只能继承,可扩展性差
  • Thread和Runnable没有返回值,而Callable有返回值
  • 继承Thread需要重写run方法,实现Runnable需要实现run方法,实现Callable接口需要实现call方法
  • 继承Thread可以直接调用start方法,而实现Runnable接口需要构建Thread对象,将该实现类对象放入Thread中,通过Thread实例调用start方法,Callable需要新建的FutureTask实例放入Thread中,通过新建的Thread实例去调用start方法,获取返回值只需要借助 FutureTask 实例调用get()方法即可!

6.线程的几种状态?

线程的基本状态:新建、就绪、运行状态、阻塞状态、死亡状态

  • 新建状态:利用NEW运算创建了线程对象,此时线程状态为新建状态,调用了新建状态线程的start()方法,将线程提交给操作系统,准备执行,线程将进入到就绪状态。

  • 就绪状态:由操作系统调度的一个线程,没有被系统分配到处理器上执行,一旦处理器有空闲,操作系统会将它放入处理器中执行,此时线程从就绪状态切换到运行时状态。

  • 运行状态:当 CPU 开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。。

  • 阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:

      等待阻塞 :运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤 醒,wait()是 Object 类的方法。
      
     同步阻塞:当线程处于运行状态时,试图获得某个对象的同步锁时,如果该对象的同步锁已经被其他线程占用,Java虚拟机就会把这个线程放到这个对象的锁池中。
     
     其他阻塞状态:当前线程执行了sleep()方法,或者调用了其他线程的join()方法,或者发出了 I/O 请求时,就会进入这个状态。线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者 I/O处理完毕时,线程重新转入就绪状态。
    
  • 死亡状态:线程一旦脱离阻塞状态时,将重新回到就绪状态,重新向下执行,最终进入到死亡状态。一旦线程对象是死亡状态,就只能被GC回收,不能再被调用。

7. sleep() 和 wait() 有什么区别?

  • 类的不同:sleep() 来自 Thread,wait() 来自 Object。

  • 释放锁:sleep() 不释放锁;wait() 释放锁。

  • 用法不同:sleep() 时间到会自动恢复;wait() 可以使用 notify()/notifyAll()直接唤醒

8.线程的 run() 和 start() 有什么区别?

  • start() 方法用于启动线程,run() 方法用于执行线程的运行时代码。
  • run() 可以重复调用,而 start() 只能调用一次。
  • 第二次调用start() 必然会抛出运行时异常

9.请问Java中可以有哪些方法来保证线程安全?

  • 加锁:比如synchronize/ReentrantLock。
  • 使用 volatile 声明变量,轻量级同步,不能保证原子性(需要解释)。
  • 使用线程安全类,例如原子类 AtomicXXX等。
  • 使用线程安全集合容器,例如:CopyOnWriteArrayList/ConcurrentHashMap等。
  • ThreadLocal本地私有变量/信号量 Semaphore等。

10.请你说一下线程状态转换相关方法: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 的方法,唤醒在对象监视器上等待的全部线程,全部唤醒

11.线程池的核心属性有哪些?

1.使用线程池的好处:
重用存在的线程,减少对象创建销毁的开销,有效的控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞,且可以定时定期执行、单线程、并发数控制,配置任务过多任务后的拒绝策略等功能。

2.类别:

  • newFixedThreadPool :一个定长线程池,可控制线程最大并发数。
  • newCachedThreadPool:一个可缓存线程池。
  • newSingleThreadExecutor:一个单线程化的线程池,用唯一的工作线程来执行任务。
  • newScheduledThreadPool:一个定长线程池,支持定时/周期性任务执行。

3.ThreadPoolExecutor构造函数里面的参数,能否解释下各个参数的作用?

  • corePoolSize:核心线程数,线程池也会维护线程的最少数量,默认情况下核心线程会一直存活,即使没有任务也不会受存
    keepAliveTime 控制!
  • 注意点:在刚创建线程池时线程不会立即启动,到有任务提交时才开始创建线程并逐步线程数目达到 corePoolSize。
  • maximumPoolSize:线程池维护线程的最大数量,超过将被阻塞!
  • 注意点:当核心线程满,且阻塞队列也满时,才会判断当前线程数是否小于最大线程数,才决定是否创建新线程
  • keepAliveTime:非核心线程的闲置超时时间,超过这个时间就会被回收,直到线程数量等于 corePoolSize。
  • unit:指定 keepAliveTime 的单位,如 TimeUnit.SECONDS、TimeUnit.MILLISECONDS
  • workQueue:线程池中的任务队列,常用的是 ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue。
  • threadFactory:创建新线程时使用的工厂
  • handler:RejectedExecutionHandler 是一个接口且只有一个方法,线程池中的数量大于
    maximumPoolSize,对拒绝任务的处理策略,默认有 4 种策略:
    1.AbortPolicy
    2.CallerRunsPolicy
    3.DiscardOldestPolicy
    4.DiscardPolicy
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值