Java面试-多线程问题整理

1.如何让线程顺序执行:

1.thread.join 方法,可以让主线程等待子线程执行完之后,再执行。 jion方法内部是调的Object.wait()方法,即让调用该方法的线程等待,一直到子线程执行完。

2.Executor.newSingleThreadExecutors().submit(Thread)方法,也可以把线程挨着顺序submit,结果也是一样。这个newSingleThreadExecutors()内部维护了一个队列,将线程存储在一个队列里面,因此也可以达到顺序执行的效果。

2. synchronized和volitale 的区别?

简单点说,volatile 能保证共享变量的内存可见性已经有序性。synchronzied保证了可见性,原子性,有序性。

理解下这三个概念,可以看下 Java并发编程实践的第2、3章,java内存模型(JMM)参考 深入理解java虚拟机12章。

JMM决定一个线程对共享变量的写入何时对另外一个线程可见,它主要定义了java线程从内存里取变量和写变量到内存的操作。java线程是通过线程的工作内存来读写主内存里的变量,读取时从工作内存的变量副本取值,如果工作内存里没有变量副本就从主内存load进来,写入时写到工作内存的变量副本再同步回主内存。

volatile 能保证共享变量的内存可见性已经有序性。也就是说定义为volatile的变量,在写操作时,会立即把新值同步回主内存,在读取前立即从主内存刷新。因为volatile变量赋值操作之后,JVM为其加了一个lock指令,这个lock指令使得当前变量副本里的值立即写入主内存,同时会让别的线程的工作内存副本无效。而且这个指令相当于一个内存屏障,在指令重排序时不能把后面的指令排到内存屏障之前,也就是volatile变量写入内存前所有操作都执行完了,才会执行后面的操作。但是volatile不能保证复合操作的原子性,如读取 修改 写入 i++。

synchronized修饰的方法或者代码块,在字节码文件中会生成一个monitorenter {代码块}monitorexit的指令,只有拿到该对象锁的线程才能访问这个代码块,其他没有拿到锁的线程会被加入一个队列,等待,直到monitorexit指令执行,会通知其他线程取获取锁。也就是synchronized是通过同一时间只能有一个线程访问该代码块来保证可见性,原子性,有序性的。

3. Lock和synchronized的区别?

1.synchronized是一个关键字,是jvm内置锁;Lock是java接口,是用java代码实现的锁。

2.synchronized锁的释放是被动的:线程执行完同步代码块或者方法,JVM会释放锁;执行出现异常,JVM也会释放锁。 Lock可以主动释放锁,需要显示调用unlock()方法。

  Lock接口有多个实现:

ReadWriteLock,读写锁,读操作用读锁,写操作用写锁,可以使得多个线程可以同时执行读操作。

ReentrantLock,可重入的,提供了以下三种功能:可中断锁,可以中断正在等待锁的线程;有条件的锁;公平、非公平锁。

ReentrantReadWriteLock

4. 线程的几种状态?

线程一共有 6 种状态(NEW、RUNNABLE、BLOCKED、WAITING、TIME_WAITING、TERMINATED)

1.初始状态 new:t=new Thread()

2.可运行状态 runnabe:t.start(),(runnabe并不代码线程就在运行了,需要线程先获得CPU时间片,在执行程序代码,此时才会运行)

3.阻塞状态:阻塞状态,表示线程进入等待状态,也就是线程因为某种原因放弃了 CPU 使用权,阻塞也分为几种情况

    1.t.sleep(),t.jion进入阻塞状态,sleep状态超时或者t.jion子线程执行完,重新进入runnable状态。

    2.o.wait(), JVM会把线程放进等待队列,wait时间结束或者被其他线程notify、notifyall,进入锁定,等到获取锁的线程重新进入runable状态

    3.线程遇到同步代码块,进入锁定状态,等到获取锁的线程重新进入runable状态

sleep不会释放锁,wait会释放锁

4. TIME_WAITING:超时等待状态,超时以后自动返回

5.结束:线程执行完毕,或者线程异常

如何测试调了某个方法后,线程处于什么状态,用jstack打印堆栈信息,即可看到线程状态。

5. Thread.join和CountDonwLatch比较:

1. join方法必须等子线程执行完成,当前线程才能继续执行。

    join方法内部是用一个while循环来不停检测join线程是否存活,存活就让主线程继续等待,直到join线程中止后,线程的this.notifyAll会被调用,主线程继续执行。

2. CountDownlatch是只要计数器变为0,调用await()的线程就就可以继续往下执行,不需要管其他线程是否执行完成,控制更灵活。

    CountDownLatch用给定的计数初始化 CountDownLatch。调用countDown()方法,计数减1。在当前计数到达零之前,调用await()方法的线程会一直受阻塞。

    当前计数到达0之后,会释放所有等待的线程。

    CyclicBarrier是可以重置计数,CountDownLatch的计数只能初始化一次。

6.ThreadPoolExecutor(这个面试问的挺多)

1. 核心类是ThreadPoolExecutor, 是线程池的实现类,可以通过Executors工具类提供的静态方法创建不同的线程池。

主要参数:corePool(核心线程池大小),maxmumPool(最大线程池大小),BlockingQueue,空闲线程存活时间

2.Executors工具类

通过Executors工具类可以创建不同的线程池ThreadPoolExecutor:SingleThreadExecutor、FixedThreadPool和CachedThreadPool。两个关键问题:

如何选择使用哪种线程池??看业务,不过个人理解FixedThreadPool这个用的多,CachedThreadPool这个会无限制

的创建线程,如果任务一直提交且其他线程一直被占用,如果任务执行可能出现这种情况,就不能用CachedThreadPool。

如何设置线程池参数?以下是网上的一些经验之谈

计算密集型: 线程数 = CPU核数+1(jdk1.8以前)  线程数 = CPU内核线程数*2(jdk1.8)

IO密集型:线程数 = CPU核心数/(1-阻塞系数),阻塞系数一般

为0.8~0.9之间,也可以取0.8或者0.9,

    套用公式,对于双核CPU来说,它比较理想的线程数就是20,当然这都不是绝对的,需要根据实际情况以及实际业务来调整

3.ThreadPoolExecutor执行顺序

当线程数小于核心线程数时,创建线程。

当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。

当线程数大于等于核心线程数,且任务队列已满

若线程数小于最大线程数,创建线程

若线程数等于最大线程数,抛出异常,拒绝任务

Executor,ReentrantLock、CountDownLatch等具体代码使用,可以看下Java编程思想第21章,有很多实例。

后续计划看下Java并发编程实战,再不定期更新。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 本文是我自己在秋招复习时的读书笔记,整理的知识点,也是为了防止忘记,尊重劳动成果,转载注明出处哦!如果你也喜欢,那...
    波波波先森阅读 13,878评论 4 56
  • 继《追风筝的人》之后,这是胡赛尼的第二本,战乱中的阿富汗,两个不同年龄段的女人因为命运而缔结在了一起,她们忍耐,妥...
    15廖淑君阅读 981评论 0 0
  • 文丨十一 图丨源自网络 1 原来你在这里,但愿后会有期。三年未见,这是李云霄见面开口说的第一句话。 什么?小鱼不解...
    11点姑娘阅读 10,110评论 10 23
  • 天地昏黄 我于其间 尘沙漫卷 遮日蔽天 我的左眼火山喷发 熔岩如瀑 右眼中星云闪烁 一瞬间宇宙生 一瞬间宇宙灭 双...
    柠小檬1314阅读 2,118评论 0 0
  • 看不清未知的旅程 通向心灵的憧憬 我在孤寂的荒野 歌唱寂寥的人生 你看那璀璨的星空 照亮了谁的梦 你是谁的梦 谁又...
    小兵黄道明阅读 1,170评论 1 4