Java多线程常见面试题

1.什么是线程

线程是操作系统能够进行运算调度的最小单位,它被包含在进程中,是进程中的实际运行单位。

2.线程与进程的区别

线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。不同的进程使用不同的内存空间,而所有线程共享一片相同的内存空间,每个线程都拥有单独的栈内存来存储本地数据。

3.Callable接口

Callable 和 Runnable的使用方法大同小异,区别在于:

   1:Callable 使用call()方法,Runnable使用run()方法

    2:call可以返回值,而run方法不能返回

    3:call 可以抛出受检查的异常,而run()不能抛出受检查异常

4.CountDownLatch、CyclicBarrier和Semaphore

CountDownLatch用法

类位于java.util.concurrent包下,利用它可以实现类似计数器的功能,比如有个任务A,让等待其他4个任务完毕之后才能执行。

CyclicBarrier用法

字面意思回环栅栏,通过他可以实现让一组线程等待至每个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。
Semaphore用法

信号量,Semaphore可以控制同时访问的线程个数,通过acquire()获取一个许可,如果没有就等待。而release()释放一个许可。

总结:

1:CountDownLatch和CycliBarrier都能够实现线程之间的等待,只不过它们侧重点不同:CountDownLatch一般用于每个线程A等待若干个线程其他线程执行完之后,它才执行;而CyclicBarrier一般用于一组线程互相等待只某个状态,然后这一组线程在同时执行;另外CountDownLatch是不能重用的,而CyclicBarrier是可以重用的。

2:Semaphore其实和锁有点类似,它一般用于控制对某组资源的权限访问。

5.java内存模型中可见性、原子性和有序性

可见性:可见性是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的,也就是一个线程修改的结果。另外一个线程马上是可见的。volatile修饰的变量不允许线程内部缓存和重排序,即直接修改内存。但是不能保证原子性。在 Java 中 volatile、synchronized 和 final 实现可见性。

原子性:具有不可分割性,在java中synchronized和在lock,unlock中保证原子性。

有序性:Java提供volatile和synchronized两个关键字来保证线程之间的有序性,volatile禁止指令重排序,synchronized 是由“一个变量在同一个时刻只允许一条线程对其进行 lock 操作”这条规则获得的,此规则决定了持有同一个对象锁的两个同步块只能串行执行。

Volatile原理:

Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程。当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。

  在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized关键字更轻量级的同步机制。

  当对非 volatile 变量进行读写的时候,每个线程先从内存拷贝变量到CPU缓存中。如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同的 CPU cache 中。

  而声明变量是 volatile 的,JVM 保证了每次读变量都从内存中读,跳过 CPU cache 这一步。

当一个变量定义为 volatile 之后,将具备两种特性:

  1.保证此变量对所有的线程的可见性,这里的“可见性”,如本文开头所述,当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。但普通变量做不到这点,普通变量的值在线程间传递均需要通过主内存(详见:Java内存模型)来完成。

  2.禁止指令重排序优化。有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障;(什么是指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理)。

volatile 性能:

  volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。

6.竞态条件

就是线程A需要判断一个变量的状态,然后根据这个变量的状态来执行某个操作,在执行这个操作之前,这个变量的状态可能会被其他线程修改。

7.Java中如何停止一个线程:

1.使用退出标志,使线程正常退出,也就是当run方法完成后线程终止,通过设置一个标志

2.使用stop方法终止线程Thread.stop(),但是使用stop方法是很危险的,可能会产生不可预料的结果。

3.使用interrupt()方法终止线程:通过interrupt中断线程是,它的中断标记会被设置为true;

    终止处于阻塞状态的线程:interrupt将线程的中断标记设为true。由于处于阻塞状态,中断标记会被清除,同时产生一个InterruptedException异常。

    终止处于运行状态的线程:1)通过中断标记终止线程

      

@Override
public void run() {
    while (!isInterrupted()) {
        // 执行任务...
    }
}

       isInterrupted是判断线程的中断标记是不是为true。当线程处于与运行状态,并且我们需要终止它时;可以通过线程的interrupt()方法,使用线程的中断标记为true,即isInterrupted()会返回true。此时会退出while循环。

      注意interrupt()方法不会终止处于运行状态的线程,它会将线程的中断标记设为true.

     2)添加额外的标记,通过设置一个flag为volatile类型,保证flag的可见性

interrupted()和isInterrupted()的区别:

interrupt()和isInterrupted()都够用于检测对象的中断标记。区别是interrupted()除了赶回中断标记外,它还会清楚中断标记(即将中断标记设为false)而isInterrupted()仅仅返回中断标记。

8.一个线程运行时发生异常会怎样?

Thread.UncaughtExceptionHandler是用于处理未捕获异常造成线程突然中断情况的一个内嵌接口。当一个捕获异常造成线程中断的时候JVM会使用Thread.getUncaughtExceptionHandle()来查询线程的UncaughtExceptionHandler并将线程和异常做为参数传递给handler的uncaughtException() 方法进行处理。

9.Java 中 notify 和 notifyAll 有什么区别?

notify是通知一个线程获取锁,只是让一个线程从wait中恢复过来,至于具体是哪个,那就看哪些线程的运气了,

notifyAll是通知所有相关的线程去竞争锁,是让所有的线程从wait中回复过来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值