Java面试题之多线程

三、多线程

35. 并行和并发有什么区别?

并行是多个事件同时进行,并发是多个事件在某一时间段内间隔发生.
你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。
你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。

36. 线程和进程的区别?

进程是操作系统资源分配的基本单位,线程是任务调度和执行的基本单位.
进程有独立的地址空间,一个进程崩溃后在保护模式下不会对其他进程产生影响,而线程只是一个进程中的不同执行路径,线程有自己的堆栈和局部变量.在操作系统中能同时运行多个进程,而在同一个进程内有多个 线程同时执行.

37. 守护线程是什么?

守护线程是服务其他线程的,在java中,线程有两种:守护线程和用户线程.
java中的jvm垃圾回收线程就是一个典型的守护线程.当用户线程全部执行完,包括main线程也执行完毕,那么 jvm会自动退出,此时守护线程也就停止了.

38. 创建线程有哪几种方式?

四种:    

  • 继承Thread类,重写run方法,用子类实例调用start()方法;
  • 实现Runnable接口并重写run方法,创建Thread实例并传入Runnable实例,调用Thread的start()方法
  • 创建Callable接口的实现类,重写call方法; 构造此实现类的实例,将其作为参数构造一个FutureTask类的实例; 以FutureTask的实例为参数构造一个Thread对象执行start()方法. 第三种方式可以允许有返回值,也可以声明抛出异常类.
  • 通过线程池创建

39. 说一下 runnable 和 callable 有什么区别?

runnable方式时,多个线程间可以共享实例变量,callable方式则不行
runnable方式没有返回值,callable有返回值
runnable方式run方法的异常只能在内部消化,callable的call()方法允许抛出异常

40. 线程有哪些状态?

1.NEW                 新建状态,此时线程还没有运行线程中的代码
2.RUNNABLE     就绪状态;处于就绪状态的线程并不一定立即运行run方法,必须还要和其他线程竞争CPU时间
3.RUNNING         运行状态;线程获得CPU时间后才进入运行状态,开始执行run方法   

4.BLOCKED          阻塞状态;线程运行过程中会有各种原因来进入阻塞状态,如:调用sleep方法进入休眠;在IO操作中被阻塞;试图得到一个锁,该锁正被其他线程持有;等待某个触发条件.阻塞状态的线程此时没有结束,暂时让出CPU时间给其他线程.
5.DEAD                 死亡状态;有两个原因导致线程死亡:第一是run方法正常退出自然死亡;第二是一个未捕获的异常终止了run方法使线程死亡. 为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法,如果是可运行或被阻塞,这个方法返回true;如果线程仍旧是new状态且不是可运行的,或者线程死亡了,则返回false。

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

1.sleep方法是Thread类的,而wait方法是Object类中的
2.sleep方法使线程暂停指定的时间,让出CPU给其他线程,但是他的监控状态依然保持着,时间到了以后会自动恢复运行状态 。在这个过程中,线程不会释放同步对象锁. 而调用wait方法,线程会放弃对象锁,进入等待队列,待调用notify/notifyAll方法后才会进入锁池,获取对象锁后才进入运行状态.

42. notify()和 notifyAll()有什么区别?

notify()方法随机唤醒一个wait线程到锁池中去竞争锁,而notifyAll方法唤醒所有wait线程到锁池.
notity()方法可能产生死锁,notifyAll则不会.

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

run()方法只是定义了线程的执行单元并非直接开启了线程资源,只有start()方法被调用,才可以启动一个线程. 如果直接调用run方法,会被当成普通方法在main线程执行,并不会创建线程.

44.创建线程池有哪几种方式?

java中的Executors可以为我们创建现成的线程池,有以下几种:
1.newSingleThreadExecutor 创建一个单线程的线程池,它只有一个工作线程,操作无界工作队列,可以保证任务顺序
2.newFixedThreadPool    创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
3.newCachedThreadPool    创建一个可缓存线程池,有空闲线程时会回收,但是任务过多,会一直创建,消耗资源
 4.newScheduledThreadPool    创建一个大小无线的线程池,此线程池支持定时以及周期性任务的需求.

45.线程池都有哪些状态?

1.RUNNING:一旦被创建,就处于此状态,可以接受新任务以及对已经添加的任务进行处理.
2.SHUTDOWN:此时不接收新任务,但是可以处理已添加的任务
3.STOP:此状态不接收新任务,不处理已添加任务,并且会中断正在处理的任务.
4.TIDYING:当所有的任务已终止,ctl记录的任务数变为0,线程池会变成tidying状态.当线程池变为TIDYING状态会执行terminated()方法.
当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。 
当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。
5. TERMINATED:线程池彻底终止会变成这个状态.当在TIDYING状态,执行完terminated()函数后,就会由TIDYING状态变为TERMINATED状态.

46. 线程池中 submit()和 execute()方法有什么区别?

1.接受参数不一样,execute()方法接收Runnable类型的参数;submit()可以接收runnable和callable类型的参数
2.submit方法有返回值,返回一个Future类型的对象,execute方法没有返回值.
3.submit方法方便处理Exception异常.

47. 在 java 程序中怎么保证多线程的运行安全?

1.最简单的方式是加入synchronized关键字,在共享数据语句中加入该关键字可以在某一时段只会让一个线程执行,其他线程不能执行.
2.使用锁Lock
3.redis?

48. 多线程锁的升级原理是什么?

        在java中,锁有三种状态,级别从低到高依次为:偏向锁、轻量级锁、重量级锁,这几个状态会随着竞争情况逐渐升级,但是不能降级.
        先说为什么要有锁升级:因为synchronized是重量级锁,每次在进行锁请求时,如果当前资源被其他线程占有,就要将当前线程阻塞,加入到阻塞队列中然后
        清空当前线程的缓存,等到锁释放时再通过notify或者notifyAll方法唤醒当前线程,让其处于就绪状态.在这个过程中是非常消耗资源的,而且有时候线程刚挂起,锁就释放了.
        而java的线程是映射到操作系统的原生线程之上的,每次线程的阻塞和唤醒都要在用户态和核心态之间转换,十分浪费资源.所以jvm对syncronized进行了优化,分为三种锁.
1.当锁对象第一次被线程获取的时候,虚拟机会将对象头中的锁标志位设置成01,并将偏向锁标志设置为1,线程通过CAS的方式将自己的ID值放到对象头中.这样每次该线程每次
        再进入锁对象的时候不用任何的同步操作,直接比较当前锁对象头中是不是该线程的ID,如果是就可以直接进入.当有其他线程来尝试获取锁的时候,发现偏向锁标示是1,说明
        锁已经被占用,则会使用CAS将对象头的偏向锁指向当前线程.
        需要注意的是,偏向锁使用一种等待竞争出现才会释放锁的机制,当有其他线程尝试获取锁的时候,才会释放锁.首先它会暂停拥有偏向锁的线程,然后检查持有偏向锁的线程
        是否活着,如果不处于活动状态,则将对象头设置为无锁状态;如果活着,那么拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,使之要么恢复到无锁要么标记对象不适合作为
        偏向锁.最后唤醒暂停的线程.
2.  假设线程1持有偏向锁,线程2来竞争偏向锁:

  • 首先线程2会检查偏向锁标记,如果是1,说明是偏向锁,那么JVM会找到线程1看其是否还活着.
  • 如果线程1已经执行完毕,那么先将偏向锁置为0,对象头设置为无锁的状态,用CAS的方式尝试将线程2的ID放入对象头,此时锁不升级,还是偏向锁.
  • 如果线程1还活着,先暂停线程1,将锁标志位变为00(轻量级锁),然后在线程1的栈帧中开辟出一块空间将对象头的Mark word置换到线程1的栈帧中,而对象头中存储的是指向当前线程栈帧的指针.此时变为轻量级锁,线程1继续执行,线程2采用CAS的方式尝试获取锁.

3.轻量级锁是通过CAS的方式尝试获取锁对象,一旦失败会先检查对象头中存储的是否是指向当前线程栈帧的指针,如果是,就可以获取锁对象,如果不是说明存在竞争那么就
        膨胀成为重量级锁.
4. 一旦有两个以上的线程竞争锁,轻量级锁就会膨胀为重量级锁,锁的状态变为10,此时对象头中存储的就是指向重量级锁的栈帧的指针,而且其他等待所的线程要进入阻塞状态,
        等待重量级锁释放后被唤醒然后去竞争.重量级锁是由操作系统来负责线程的调度,会消耗大量的系统资源.

49. 什么是死锁?

死锁是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种相互等待的过程,如果没有外力作用,他们将无法推进下去.

50. 怎么防止死锁?

1.以确定的顺序获取锁
2.超时放弃.(Lock锁中就使用了这种方式)
3.银行家算法

51. ThreadLocal 是什么?有哪些使用场景?

也称为线程本地变量,ThreadLocal在每个线程中对一个变量创建了一个副本,且在线程内部任何地方都可以使用,线程间互不影响. 应用场景:spring的声明式事物管理.

52. 说一下 synchronized 底层实现原理?

首先,每个对象都有一个监视器锁(monitor),当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

  •   如果monitor的进入数为0,则线程进入monitor,将进入数设置为1,该线程为monitor的所有者
  •   如果线程已经占有monitor,只是重新进入,则进入数+1
  •   如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获得所有权

    执行monitorexit的线程必须是objectref所对应的monitor的所有者.指令执行时,monitor的进入数-1,如果减为0,那么线程退出monitor,不再是持有者.
    在将synchronized代码块反编译以后,我们可以发现调用了monitorenter和monitorexit指令.
    所以如果synchronized同步代码块,底层是调用monitor的两个指令来实现锁
    如果synchronized同步方法,底层是读取运行时常量池的ACC_SYNCHRONIZED标志来实现的.

53. synchronized 和 volatile 的区别是什么?

  • volatile修饰的变量,jvm每次都从主内存读取,不会从工作内存读取 ,而synchronized是锁住当前变量,同一时刻只有一个线程能访问当前变量.
  • volatile只是作用于变量,synchronized可以用在变量,方法中
  • valatile仅能实现变量修改的可见性,无法保证原子性. synchronized可以实现变量修改的可见性和原子性.

54. synchronized 和 Lock 有什么区别?

  • synchronized是java内置的关键字,Lock是个接口
  • synchronized无法判断是否获取锁的状态,Lock可以判断是否获取到锁.
  • synchronized会自动释放锁,Lock需要手动释放
  • synchronized可重入(同一个类中两个同步方法,获取到锁后不用每次都去获取),不可中断,非公平;Lock可中断可公平.
  • synchronized获得锁的线程阻塞,其他线程都会无限等待,Lock不会

55. synchronized 和 ReentrantLock 区别是什么?

  • synchronized遇到异常不catch,锁会自动释放,ReentrantLock需要手动释放
  • ynchronized是非公平锁,ReentrantLock可以实现公平锁.
  • 前者无法获取锁的状态,后者可以,tryLock()方法可以返回是否获得了锁.
  • ReentrantLock可以精确唤醒线程,Synchronized要么随机唤醒一个,要么全部唤醒.

56. 说一下 atomic 的原理?

atomic包用中的类可以实现多线程环境下的变量操作,底层是调用CPU的CAS指令来进实现线程安全的. CAS,比较并操作,每次在set前,对比一下当前值和预期值是否一样,一样则set,否则认为失败,循环对比直到成功.

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值