1.并行和并发有什么区别?
- 并行在操作系统中是指,一组程序按独立异步的速度执行,不等于时间上的重叠(同一个时刻发生)。
- 在同一个时间段内,两个或多个程序执行,有时间上的重叠(宏观上是同时,微观上仍是顺序执行)。并发的实质是一个物理CPU(也可以多个物理CPU) 在若干道程序之间多路复用,并发性是对有限物理资源强制行使多用户共享以提高效率。
2.线程和进程的区别?
- 进程是资源分配的最小单位,线程是CPU调度的最小单位
- 一个线程只能属于一个进程,但是一个进程可以拥有多个线程。多线程处理就是允许一个进程中在同一时刻执行多个任务。
3.守护线程是什么?
- 守护线程,专门用于服务其他的线程,如果其他的线程(即用户自定义线程)都执行完毕,连main线程也执行完毕,那么jvm就会退出(即停止运行)——此时,连jvm都停止运行了,守护线程当然也就停止执行了。
- 守护线程(即daemon thread),是个服务线程,准确地来说就是服务其他的线程,这是它的作用——而其他的线程只有一种,那就是用户线程。所以java里线程分2种,
- 1、守护线程,比如垃圾回收线程,就是最典型的守护线程。
- 2、用户线程,就是应用程序里的自定义线程。
4.创建线程有哪几种方式?
- 继承Thread类创建线程类
- 通过Runnable接口创建线程类
- 通过Callable和Future创建线程
5.线程有哪些状态?
线程状态有 5 种,新建,就绪,运行,阻塞,死亡。
6.sleep() 和 wait() 有什么区别?
- 1、这两个方法来自不同的类分别是Thread和Object,sleep方法属于Thread类中的静态方法,wait属于Object的成员方法。
- 2、sleep()是线程类(Thread)的方法,不涉及线程通信,调用时会暂停此线程指定的时间,但监控依然保持,不会释放对象锁,到时间自动恢复;wait()是Object的方法,用于线程间的通信,调用时会放弃对象锁,进入等待队列,待调用notify()/notifyAll()唤醒指定的线程或者所有线程,才进入对象锁定池准备获得对象锁进入运行状态。
- 3、wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用(使用范围)。
- 4、sleep()方法必须捕获异常InterruptedException,而wait()\notify()以及notifyAll()不需要捕获异常。
7.说一下 runnable 和 callable 有什么区别?
- Runnable 接口 run 方法无返回值;Callable 接口 call 方法有返回值,支持泛型
- Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call 方法允许抛出异常,可以获取异常信息
8.notify()和 notifyAll()有什么区别?
- 等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池,等待池中的线程不会去竞争该对象的锁。
- 锁池:只有获取了对象的锁,线程才能执行对象的 synchronized 代码,对象的锁每次只有一个线程可以获得,其他线程只能在锁池中等待
- notify() 方法随机唤醒对象的等待池中的一个线程,进入锁池;notifyAll() 唤醒对象的等待池中的所有线程,进入锁池。
9.线程的 run()和 start()有什么区别?
- 调用 start() 方法是用来启动线程的,轮到该线程执行时,会自动调用 run();直接调用 run() 方法,无法达到启动多线程的目的,相当于主线程线性执行 Thread 对象的 run() 方法。
- 一个线程对线的 start() 方法只能调用一次,多次调用会抛出 java.lang.IllegalThreadStateException 异常;run() 方法没有限制。
10.创建线程池有哪几种方式?
- 通过Executors工厂方法创建,参考:java中线程池创建的几种方式
- Executors目前提供了5种不同的线程池创建配置:
1、newCachedThreadPool(),它是用来处理大量短时间工作任务的线程池,具有几个鲜明特点:它会试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程;如果线程闲置时间超过60秒,则被终止并移除缓存;长时间闲置时,这种线程池,不会消耗什么资源。其内部使用SynchronousQueue作为工作队列。
2、newFixedThreadPool(int nThreads),重用指定数目(nThreads)的线程,其背后使用的是无界的工作队列,任何时候最多有nThreads个工作线程是活动的。这意味着,如果任务数量超过了活动线程数目,将在工作队列中等待空闲线程出现;如果工作线程退出,将会有新的工作线程被创建,以补足指定数目nThreads。
3、newSingleThreadExecutor(),它的特点在于工作线程数目限制为1,操作一个无界的工作队列,所以它保证了所有的任务都是被顺序执行,最多会有一个任务处于活动状态,并且不予许使用者改动线程池实例,因此可以避免改变线程数目。
4、newSingleThreadScheduledExecutor()和newScheduledThreadPool(int corePoolSize),创建的是个ScheduledExecutorService,可以进行定时或周期性的工作调度,区别在于单一工作线程还是多个工作线程。
5、newWorkStealingPool(int parallelism),这是一个经常被人忽略的线程池,Java 8 才加入这个创建方法,其内部会构建ForkJoinPool,利用Work-Stealing算法,并行地处理任务,不保证处理顺序。
- 通过new
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue)自定义创建
11.线程池都有哪些状态?
线程池的5种状态:RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED。
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
12.线程池中 submit()和 execute()方法有什么区别?
- execute() 参数 Runnable ;submit() 参数 (Runnable) 或 (Runnable 和 结果 T) 或 (Callable)
- execute() 没有返回值;而 submit() 有返回值
- submit() 的返回值 Future 调用get方法时,可以捕获处理异常
13.在 java 程序中怎么保证多线程的运行安全?
线程的安全性问题体现在:
- 原子性:一个或者多个操作在 CPU 执行的过程中不被中断的特性
- 可见性:一个线程对共享变量的修改,另外一个线程能够立刻看到
- 有序性:程序执行的顺序按照代码的先后顺序执行
导致原因:
- 缓存导致的可见性问题
- 线程切换带来的原子性问题
- 编译优化带来的有序性问题
解决办法:
- JDK Atomic开头的原子类、synchronized、LOCK,可以解决原子性问题
- synchronized、volatile、LOCK,可以解决可见性问题
- Happens-Before 规则可以解决有序性问题
14.多线程锁的升级原理是什么?
- 锁的级别从低到高:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁
- synchronized锁升级的原理:在锁对象的对象头里有一个threadid字段,在第一次访问的时候threadid为空,jvm让其持有偏向锁,并将threadid设置为其线程id,再次进入的时候会先判断threadid是否与其线程id一致,如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为轻量级锁,通过自旋循环一定次数来获取锁,执行一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了synchronized锁的升级。
- 锁的升级的目的:锁升级是为了减低锁带来的性能消耗。在Java6之后优化了synchronized的实现方式,使用了偏向锁升级为轻量级锁再升级到重量级锁的方式,从而减低了锁带来的性能消耗。
参考:多线程锁的升级原理是什么?
15.什么是死锁?
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
16.怎么防止死锁?
- 尽量使用 tryLock(long timeout, TimeUnit unit)的方法(ReentrantLock、ReentrantReadWriteLock),设置超时时间,超时可以退出防止死锁。
- 尽量使用 Java. util. concurrent 并发类代替自己手写锁。
- 尽量降低锁的使用粒度,尽量不要几个功能用同一把锁。
- 尽量减少同步的代码块。
- 设置优先级
- 超时放弃 ReentrantLock接口中: boolean tryLock(long time, TimeUnit unit) 设置超时时间,超时可以退出防止死锁。
- 特定顺序 A->B->C
- 尽量降低锁的使用粒度,尽量不要几个功能用同一把锁,能锁块不锁方法
- 不要用锁
- 用Concurrent类。比较常用的是ConcurrentHashMap、ConcurrentLinkedQueue、原子操作。AtomicBoolean等,实际应用中java.util.concurrent.atomic十分有用,简单方便且效率比使用Lock更高
17.ThreadLocal 是什么?有哪些使用场景?
- ThreadLocal 是线程本地存储,在每个线程中都创建了一个 ThreadLocalMap 对象,每个线程可以访问自己内部 ThreadLocalMap 对象内的 value。
- 经典的使用场景是为每个线程分配一个 JDBC 连接 Connection。这样就可以保证每个线程的都在各自的 Connection 上进行数据库的操作,不会出现 A 线程关了 B线程正在使用的 Connection; 还有 Session 管理 等问题。
参考:ThreadLocal有什么作用?有哪些使用场景?
18.说一下 synchronized 底层实现原理?
19.synchronized 和 volatile 的区别是什么?
- volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
- volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的
- volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性
- volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
- volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化
20.synchronized 和 Lock 有什么区别?
1、原始构成:
- synchronized是关键字,属于JVM层面,底层是由一对monitorenter和monitorexit指令实现的。
- ReentrantLock是一个具体类,是API层面的锁。
2、使用方法:
- synchronized不需要用户手动释放锁,当synchronized代码块执行完成后,系统会自动让线程释放对锁的占用
- ReentrantLock需要用户手动释放锁,若没有手动释放可能导致死锁现象。
3、等待是否可中断:
- synchronized不可中断,除非抛出异常或者正常运行完成
- ReentrantLock可中断
4、加锁是否公平:
- synchronized非公平锁
- ReentrantLock两者都可以,默认是非公平锁。
5、锁绑定多个条件Condition:
- synchronized没有。
- ReentrantLock可用来分组唤醒需要唤醒的线程。而不是像synchronized要么随机唤醒一个线程,要么唤醒所有线程。
参考:java面试-synchronized与lock有什么区别?
21.synchronized 和 ReentrantLock 区别是什么?
这两种方式最大的区别就是对于synchronized来说,它是Java语言关键字,是原生语法层面的互斥,需要jvm实现。而ReentrantLock他是jdk1.5之后提供的API层面的互斥锁,需要lock()和unlock()方法配合try/finally语句来完成。
参考:synchronized和ReentrantLock的区别
22.说一下 atomic 的原理?
- 1、直接操作内存,使用Unsafe 这个类
- 2、使用 getIntVolatile(var1, var2) 获取线程间共享的变量
- 3、采用CAS的尝试机制(核心所在),代码如下:
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
可以看到这个do .... while {!this.compareAndSwapInt(var1, var2, var5, var5 + var4)} 。不断地使用CAS进行重试,直到执行成功为止。这里是一个乐观锁的操作。
- 4、使用Atomic ,是在硬件上、寄存器尽心阻塞,而不是在线程、代码上阻塞。
- 5、这个是通俗说法ABA的问题
参考:atomic的实现原理