1.并行和并发有什么区别?
- 并发:是指多个线程任务在同一个CPU上快速地轮换执行,由于切换的速度非常快,给人的感觉就是这些线程任务是在同时进行的,但其实并发只是一种逻辑上的同时进行;
- 并行:是指多个线程任务在不同CPU上同时进行,是真正意义上的同时执行。
2.线程和进程的区别?
- 线程则是指进程中的一个执行流程,有时也称为执行情景。一个进程可以由多个线程组成,即在一个进程中可以同时运行多个不同的线程,它们分别执行不同的任务。当进程内的多个线程同时运行时,这种运行方式称为并发运行。
- 进程是指运行中的应用程序,每一个进程都有自己独立的内存空间。例如,对于IE浏览器程序,每打开一个IE浏览器窗口,就启动了一个IE浏览器进程。同样每次执行JDK的java.exe程序,就启动了一个独立的Java虚拟机进程,该进程的任务是解析并执行Java程序代码。
- 线程与进程的主要区别在于:每个进程都需要操作系统为其分配独立的内存地址空间,而同一进程中的所有线程在同一块地址空间中工作,这些线程可以共享同一块内存和系统资源,比如共享一个对象或者共享已经打开的一个文件。
3.守护线程是什么?
Java线程分为用户线程和守护线程。
守护线程是程序运行的时候在后台提供一种通用服务的线程。所有用户线程停止,进程会停掉所有守护线程,退出程序。
Java中把线程设置为守护线程的方法:在 start 线程之前调用线程的 setDaemon(true) 方法。
典型的守护线程:Java垃圾回收线程
4.创建线程有哪几种方式?
- 继承Thread类型重写run 方法。
- 实现Runnable接口。
- 实现Callable接口。
5.说一下 runnable 和 callable 有什么区别?
- 实现Callable接口的任务线程能返回执行结果;而实现Runnable接口的任务线程不能返回结果。
- Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛。
6.线程有哪些状态?
线程状态有 5 种,新建,就绪,运行,阻塞,死亡。关系图如下:
- 新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
- 就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
- 运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
- 阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
- 等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
- 同步阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
- 其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
- 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
7.sleep() 和 wait() 有什么区别?
- sleep():让正在执行的线程主动让出 cpu, cpu 去执行其他线程,在 sleep 指定的时间过后,cpu 才会回到这个线程上继续往下执行,如果当前线程进入了同步锁, sleep 方法并不会释放锁,即使当前线程使用 sleep 方法让出了 cpu,但其他被同步锁挡住了的线程也无法得到执行。
- wait():是指在一个已经进入了同步锁的线程内,让自己暂时让出同步锁,以便其他正在等待此锁的线程可以得到同步锁并运行,只有调用了 notify 方法才能唤醒等待线程,等待线程就会解除 wait 状态继续争夺同步锁向下运行。
8.notify()和 notifyAll()有什么区别?
- 如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
- 当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争。
9.线程的 run()和 start()有什么区别?
- run():是线程中的一个执行方法,如果直接调用线程类的run()方法,此时run()方法仅仅被当做一个普通的函数调用,并不会启动线程。
- start():是Thread类的一个方法,通过调用start()方法可以启动一个线程,这时被启动的线程就会进入就绪状态,等分配到CPU时间片后就会执行线程里的run()方法。
10.创建线程池有哪几种方式?
6种,分别如下:
- newFixedThreadPool:定长线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这时线程数量不再变化,当线程发生错误结束时,线程池会补充一个新的线程。
- newCachedThreadPool:可缓存的线程池,如果线程池的容量超过了任务数,自动回收空闲线程,任务增加时可以自动添加新线程,线程池的容量不限制。
- newScheduledThreadPool:定长线程池,可执行周期性的任务。
- newSingleThreadExecutor:单线程的线程池,线程异常结束,会创建一个新的线程,能确保任务按提交顺序执行。
- newSingleThreadScheduledExecutor:单线程可执行周期性任务的线程池。
- newWorkStealingPool:任务窃取线程池,不保证执行顺序,适合任务耗时差异较大。线程池中有多个线程队列,有的线程队列中有大量的比较耗时的任务堆积,而有的线程队列却是空的,就存在有的线程处于饥饿状态,当一个线程处于饥饿状态时,它就会去其它的线程队列中窃取任务。解决饥饿导致的效率问题。
11.线程池都有哪些状态?
线程池的5种状态:Running、ShutDown、Stop、Tidying、Terminated。
- RUNNING:这是最正常的状态,接受新的任务,处理等待队列中的任务。
- SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务。
- STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。
- TIDYING:所有的任务都销毁了,workCount 为 0,线程池的状态在转换为 TIDYING 状态时,会执行钩子方法 terminated()。
- TERMINATED:terminated()方法结束后,线程池的状态就会变成这个。
12.线程池中 submit()和 execute()方法有什么区别?
- execute() 参数 Runnable ;submit() 参数 (Runnable) 或 (Runnable 和 结果 T) 或 (Callable)。
- execute() 没有返回值;而 submit() 有返回值。
- submit() 的返回值 Future 调用get方法时,可以捕获处理异常。
13.在 java 程序中怎么保证多线程的运行安全?
- 使用安全类,比如 Java. util. concurrent 下的类。
- 使用自动锁 synchronized。
- 使用手动锁 Lock。
14.多线程锁的升级原理是什么?
多线程锁锁有4种状态:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态(级别从低到高)。
偏向锁 | 轻量级锁 | 重量级锁 | |
适用场景 | 只有一个线程进入同步块 | 虽然很多线程,但是没有冲突:多条线程进入同步块,但是线程进入时间错开因而并未争抢锁 | 发生了锁争抢的情况:多条线程进入同步块并争用锁 |
本质 | 取消同步操作 | CAS操作代替互斥同步 | 互斥同步 |
优点 | 不阻塞,执行效率高(只有第一次获取偏向锁时需要CAS操作,后面只是比对ThreadId) | 不会阻塞 | 不会空耗CPU |
缺点 | 适用场景太局限。若竞争产生,会有额外的偏向锁撤销的消耗 | 长时间获取不到锁空耗CPU | 阻塞,上下文切换,重量级操作,消耗操作系统资源 |
- 当给一个对象增加synchronized锁之后,相当于上了一个偏向锁。
- 当有一个线程去请求时,就把这个对象MarkWord的ID改为当前线程指针ID(JavaThread),只允许这一个线程去请求对象。当有其他线程也去请求时,就把锁升级为轻量级锁。每个线程在自己的线程栈中生成LockRecord,用CAS自旋操作将请求对象MarkWordID改为自己的LockRecord,成功的线程请求到了该对象,未成功的对象继续自旋。
- 如果竞争加剧,当有线程自旋超过一定次数时(在JDK1.6之后,这个自旋次数由JVM自己控制),就将轻量级锁升级为重量级锁,线程挂起,进入等待队列,等待操作系统的调度
15.什么是死锁?
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。是操作系统层面的一个错误,是进程死锁的简称,最早在 1965 年由 Dijkstra 在研究银行家算法时提出的,它是计算机操作系统乃至整个并发程序设计领域最难处理的问题之一。
16.怎么防止死锁?
- 尽量使用 tryLock(long timeout, TimeUnit unit)的方法(ReentrantLock、ReentrantReadWriteLock),设置超时时间,超时可以退出防止死锁。
- 尽量使用 Java. util. concurrent 并发类代替自己手写锁。
- 尽量降低锁的使用粒度,尽量不要几个功能用同一把锁。
- 尽量减少同步的代码块。
17.ThreadLocal 是什么?有哪些使用场景?
ThreadLocal 是线程本地存储,在每个线程中都创建了一个 ThreadLocalMap 对象,每个线程可以访问自己内部 ThreadLocalMap 对象内的 value。
经典的使用场景是为每个线程分配一个 JDBC 连接 Connection。这样就可以保证每个线程的都在各自的 Connection 上进行数据库的操作,不会出现 A 线程关了 B线程正在使用的 Connection; 还有 Session 管理 等问题。
18.说一下 synchronized 底层实现原理?
- 同步代码块是通过 monitorenter 和 monitorexit 指令获取线程的执行权
- 同步方法通过加 ACC_SYNCHRONIZED 标识实现线程的执行权的控制
19.synchronized 和 volatile 的区别是什么?
- synchronized 可以作用于变量、方法、对象;volatile 只能作用于变量。
- synchronized 可以保证线程间的有序性(猜测是无法保证线程内的有序性,即线程内的代码可能被 CPU 指令重排序)、原子性和可见性;volatile 只保证了可见性和有序性,无法保证原子性。
- synchronized 线程阻塞,volatile 线程不阻塞。
20.synchronized 和 Lock 有什么区别?
- synchronized是Java语法的一个关键字,加锁的过程是在JVM底层进行。Lock是一个类,是JDK应用层面的,在JUC包里有丰富的API。
- synchronized在加锁和解锁操作上都是自动完成的,Lock锁需要我们手动加锁和解锁。
- Lock锁有丰富的API能知道线程是否获取锁成功,而synchronized不能。
- synchronized能修饰方法和代码块,Lock锁只能锁住代码块。
- Lock锁有丰富的API,可根据不同的场景,在使用上更加灵活。
- synchronized是非公平锁,而Lock锁既有非公平锁也有公平锁,可以由开发者通过参数控制。
22.说一下 atomic 的原理?
atomic 可以实现一些简单的数据同步,Atomic包中的原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式。
原理:通过CAS乐观锁保证原子性,通过自旋保证当次修改的最终修改成功,通过降低锁粒度(多段锁)增加并发性能。