- 1.线程的6种状态
(1)New:初始状态,线程被创建,但是还没调用start方法。
(2)Running:就绪状态和运行状态,统称为运行状态
(3)Blocked:阻塞状态
(4)Waiting:等待状态,需要等待其他线程做出特定的动作(通知或中断)。
(5)Time-Waiting:超时等待状态,表示可以在指定的时间自行返回。
(6)Terminated:终止状态,表示当前线程已经执行完毕。
- 2.线程间的通信方式
(1)使用volatile修饰字段,保证所有线程对变量访问的可见性,程序对该变量的访问需要从共享内存中获取,变更必须同步刷新共享内存。
(2)使用Synchronized关键字修饰方法或代码块,保证多线程环境下只有一个线程处于方法或代码块中,保证线程对变量访问的可见性和排他性。
(3)Java内置了等待 wait() /通知 notify() 机制,实现一个线程修改一个对象的值,另一个线程感知到变化,进行相应的操作。
(4)使用Thread.join方法,来保证另一个线程需等待调用join方法的线程终止。
(5)使用ThreadLocal线程变量,来保证线程间变量的隔离性。
(6)使用管道输入/输出流,用于线程之间的数据传输,传输的媒介为内存。具体实现:PipedOutputStream、PipedInputStream、PipedReader和PipedWriter,前两种面向字节,而后两种面向字符。
- 3.ThreadLocal理解 ThreadLocal<T>
(1)Thread类中有一个类型为ThreadLocal.ThreadLocalMap的实例变量,每个线程都有一个属于自己的ThreadLocalMap。
(2)ThreadLocalMap内部维护着Entry数组,每个Entry代表的是一个完整的对象。
(3)每个线程往ThreadLocal设置值时,ThreadLocal本身不存储值,其实都是往自己的ThreadLocalMap存储,读取也是在自己的map里找对应的key。
(4)key为弱引用,value是强引用;如果使用不当会存在内存泄漏,所以需要用完后调用remove方法。
- 4.锁的使用
(1)Synchronized:
修饰实例方法:作用于当前对象实例加锁,进入同步代码前要获取当前对象实例的锁。
修饰静态方法:作用于所有类的实例对象,是给当前类加锁,会进入同步代码块前要先获取当前class的锁。
修饰代码块:给给定对象加锁,如果参数是this或某个对象实例,则是实例锁;如果是类.class,则为类锁。
示例说明:
public synchronized void handle() 表示要获取当前对象实例的锁才能进入该同步方法。
public static synchronized void handle() 表示要获取当前calss的锁才能进入该同步静态方法。
synchronized(this||object) 表示要获取给定对象实例的锁才能进入同步代码块。
synchronized(类.class) 表示要获取给定class的锁才能进入同步代码块。
锁优化:1.6之前是重量级锁;之后进行了锁优化。
锁升级方向:无锁->偏向锁->轻量级锁->重量级锁。
synchronized与wait和notify方法结合实现等待 wait() /通知 notify()/notifyAll() 机制。
(2)Lock接口实现类ReentrantLock:可重入锁,可以使用构造函数设置参数来指定是公平锁还是非公平锁。
常用方法: lock() 、 unock() 。
ReentrantLock借助于Condition接口和newCondition方法实现等待 await() /通知 signal() 机制。
非公平锁:调用CAS进行抢锁,如果锁没有被占用,则直接占用锁。
公平锁:先判断等待队列是否有线程处理等待状态,如果有就不去抢锁,否则就调用CAS进行抢锁。
(3)ReadWriteLock接口实现类ReentrantReadWriteLock:读写锁
原则上读读不互斥、读写互斥、写写互斥,提供了 readLock() 、 writeLock() 分别用于获取读锁、写锁。
ReadWriteLock lock = new ReentrantReadWriteLock(); Lock readLock = lock.readLock();// 读锁 Lock writeLock = lock.writeLock();// 写锁
- 5.notify()和notifyAll()区别
notify():随机唤醒一个在该共享变量上由于调用wait方法后被挂起的线程。
notifyAll():唤醒所有在该共享变量上由于调用wait方法后被挂起的线程。
- 6.对AQS的了解
抽象同步队列,简称AQS,是一个先进先出(FIFO)的双向链表队列。
用于在多线程环境下,通过共享资源状态(state)和先进先出的等待队列来实现多线程访问共享资源的同步框架。
应用如下:
Semaphore信号量,state指的就是剩余的许可证数量;
ReentrantLock可重入锁,state值的是锁占有情况和重入次数计数;
CountdownLatch计数器,倒计时剩余值。
- 7.CAS了解
CAS是一种实现线程安全的算法,可以表述为“如果预期值等于内存值,则修改为新值;否则继续循环进入下一次判断设置”。
CAS的操作底层是通过调用sun.misc.Unsafe类中的CompareAndSwap的方法来保证线程安全的。
上述过程,可能出现ABA问题,怎么解决?
ABA问题是两个线程中有一个线程1更新了多次,A->B->A,在这个过程中,另一个线程2可能第一次在A还没有更新为B时拿到了A,后面比对时候又看到了内存中的是多次更新后的A。
这种情况可以使用版本号控制来解决。
- 8.用过线程池吗?
ThreadPoolTaskExecutor ThreadPoolExecutor
线程池的核心参数:
(1)核心线程数:CPU核数*2
(2)线程池等待队列,使用LinkedBlockingDeque
(3)最大线程数:核心线程数*2
(4)非核心闲置线程存活时间
(5)拒绝策略,使用ThreadPoolExecutor$AbortPolicy,直接丢弃任务,并抛出RejectedExecutionException异常
线程池的工作流程:
(1)线程池刚创建的时候,里面没有一个线程。
(2)调用execute方法添加一个任务时,线程池会计算正在运行的线程数。
(3)如果正在运行的线程数小于核心线程数,则会立即创建一个核心线程执行任务。
(4)如果大于核心线程数,则判断等待队列是否已满,如果没有满,则会加入等待队列。
(5)如果等待队列已满,则需判断正在运行的线程数是否大于最大线程数。
(6)如果不大于最大线程数,则创建非核心线程执行任务。
(7)如果当前等于最大线程数,且会根据拒绝策略做相应处理。
(8)如果后面线程闲置了,且大于设置的存活时间,则该线程会被停掉。
(9)当所有任务被完成后,线程数会缩小到设置的核心线程数。
- 9.线程池异常怎么处理
常见的有try-catch捕获异常、submit执行,Future.get()获取异常。
- 10.创建线程的几种方式
(1)直接使用Thread,或继承Thread类
(2)FutureTask实现Callable或Runnable接口
(3)ComplatableFuture,默认使用的ForkJoinPool线程池,可指定线程池ThreadPoolExecutor
- 11.CountDownLatch
倒计时计数器, new CountDownLatch(int size) 初始化共享锁
(1)每个子线程执行完在finally代码块调用 countDown() 方法,使计数递减,每执行一次减1,直到0为止。
(2)主线程调用 await() 阻塞线程,当count=0时,唤醒正在等待的线程,可用来确保主线程等待子线程都完成后再执行。
应用场景:非常适合于对任务进行拆分,使其并行执行,总的执行时间取决于执行最慢的任务线程耗时。
- 12.FutureTask
实现Callable或Runnable接口,搭配new Thread或线程池使用。
public class FutureTaskRunnable implements Runnable { @Override public void run() { System.out.println("FutureTask.run()"); } } public class FutureTaskCallable implements Callable<Integer> { @Override public Integer call() throws Exception { System.out.println("FutureTask.call()"); return 1; } }
(1) new Thread(new FutureTaskRunnable()).start();
(2) Executors.newFixedThreadPool(1).execute(new FutureTaskRunnable());
(3) Future<Integer> future = Executors.newFixedThreadPool(1).submit(new FutureTaskCallable());
future .get() 方法获取返回值, get() 方法会阻塞主线程,如果异步任务非常耗时,会导致程序吞吐量下降。
- 13.Fork/Join
(1)采用的是分而治之的思想,将一个规模为N的问题分解为K个规模较小的子问题,并对子问题的结果进行汇总。
(2)充分利用多核CPU,每个工作线程有自己的任务队列,如果它处理完自己的队列后,会去帮助其他工作线程处理任务。
(3)每个工作线程的任务队列是双端队列,自己是从头部获取任务,其他工作线程帮忙处理任务时,则是从尾部获取任务。
- 14.ComplatableFuture
原理:异步执行主任务,将依赖任务加入到链表中,主任务执行完从链表中获取依赖任务执行。
其中依赖任务在加入链表时,会判断主任务是否执行完,如果执行完则立即执行依赖任务,不需要添加到链表。
可创建有返回值或无返回值的任务:
(1) CompletableFuture<U> supplyAsync(Supplier<U> supplier):有返回值;
(2) CompletableFuture<Void> runAsync(Runnable runnable):无返回值;
可以组合执行多个任务
(1) CompletableFuture<V> thenCombine(CompletionStage<? extends U> other, BiFunction<? super T, ? super U, ? extends V> fn) 组合两个 future,获取两个 future 的返回结果,并返回当前任务的返回值
(2) CompletableFuture.allOf(future1, future2).join() 等待指定的所有任务都完成。
- 15.Exchanger:在两个线程运行的过程中交换数据。