文章目录
https://thinkwon.blog.youkuaiyun.com/article/details/104863992
1、wait/sleep的区别:
- 来自不同的类:
wait => Object
sleep => Thread - 关于锁的释放:
wait会释放锁,sleep不会释放锁 - 使用范围:
wait必须在同步代码块中使用,而sleep可以在任何地方使用 - 是否需要捕获异常:
wait不需要捕获异常,sleep需要捕获异常 - 同:都是让线程进入等待状态。
2、synchronized出现异常会释放锁?
会释放。
3、synchronized和Lock的区别?
- synchronized是关键字,Lock是接口;
- synchronized是不可中断的,Lock是可中断的,也可以是不可中断的;
- synchronized会自动释放锁,Lock锁需要手动释放锁;
- synchronized是非公平锁,Lock锁可以是公平锁也可以是非公平锁‘;
- Lock可以知道线程有没有获取锁,而synchronized不能;
- synchronized可以锁代码块和方法,Lock锁只能锁代码块;
4、Runnable和Callable的区别?
- Callable有返回值,而Runnable没有返回值
- Callable可以抛出异常,而Runnable不能抛出异常
- Callable的方法是call(),而Runnable的方法是run()
为什么Runnable接口的run方法不能抛出异常,而必须捕获呢?
因为Runnable接口中的run方法没有抛出异常,所以子类在重写run方法时,不能抛出异常。
5、为什么内部类不能访问非final的局部变量?
局部变量的生命周期:当该方法被调用时,该方法中的局部变量在栈中被创建,当方法调用结束时,退栈,这些局部变量全部死亡。而内部类对象生命周期与其它类对象一样:自创建一个匿名内部类对象,系统为该对象分配内存,直到没有引用变量指向分配给该对象的内存,它才有可能会死亡(被JVM垃圾回收)。所以完全可能出现的一种情况是:成员方法已调用结束,局部变量已死亡,但匿名内部类的对象仍然活着。
6、阻塞队列方法的区别?
阻塞队列的使用:
操作 | 抛出异常 | 有返回值(true/false) | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add() | offer() | put() | offer(E e, long timeout, TimeUnit unit) |
移除 | remove() | poll() | take() | poll(long timeout, TimeUnit unit) |
获取队首元素 | element() | peek() |
当使用add()往一个满的队列中加入元素时,会抛出如下的异常:
抛出异常:
java.lang.IllegalStateException: Queue full
当使用remove()移除一个空队列的元素时,或者使用element()获取一个空队列的队首元素时,会抛出如下异常:
队列为空:
java.util.NoSuchElementException
使用offer()往队列添加元素,添加成功返回true,队满添加失败,返回false。
使用poll()出队元素时,或者peek()获取队首元素时,如果元素存在,则返回元素,否则返回null。
当使用put()往队列添加元素时,如果队列不满则添加成功,否则会阻塞(一直阻塞)。
当使用take()出队元素时,如果队列有元素则出队,并返回该元素,如果队列为空,调用该方法则会阻塞(一直阻塞)。
offer(E e, long timeout, TimeUnit unit) 可以指定超时等待的时间,不会一直等待下去。
poll(long timeout, TimeUnit unit) 也一样。
7、线程池详解
https://blog.youkuaiyun.com/Linging_24/article/details/109401629
8、自定义线程池时,我们如何确定线程池的最大线程数量?
-
CPU密集型:
CPU是几核的就是几,可以保持CPU的效率最高。
Runtime.getRuntime().availableProcessors(); //获取CPU最大并行线程的数量。
-
IO密集型:
判断我们程序中非常耗IO的任务数量,比这个数大就可以,可以设置为2倍。
使用多线程时,主要是为了提高CPU的利用率。
我们的任务一般分为计算密集型和IO密集型。
计算密集型:主要是需要CPU的频繁计算,来产生结果的,所以任务越多,我们CPU的切换所需要消耗的资源就越多,所以一般保持和CPU核心数的任务或者+1这样就可以,每个任务就是一个线程,所以对于计算密集型任务来说,线程的最大数量就是CPU核心数量或者+1。
IO密集型:IO密集型任务的特点是CPU的使用率比较低,一般时间都浪费在网络连接或者磁盘操作等原因,所以任务越多CPU的利用率就越高,但是也有限度,一般设置为CPU核心数量的2倍即可。
9、volatile如何保证可见性和有序性?
https://blog.youkuaiyun.com/bfj11/article/details/123949405
https://blog.youkuaiyun.com/jyxmust/article/details/76946283
有序性:lock指令前缀 + 内存屏障
可见性:内存屏障
内存屏障的两个作用:
- 禁止指令重排序
- 读写强刷,立即可见
10、synchronized详解
https://blog.youkuaiyun.com/Linging_24/article/details/110541592
11、JUC三大辅助类
- CountDownLatch:实现让一个线程等待其他线程执行完毕后再执行。
- CyclicBarrier:实现让一组线程等待至某一状态,再全部同时执行。
- Semaphore:Semaphore 是 synchronized 的加强版,作用是控制线程的并发数量。
12、线程的状态
-
新建状态:
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。 -
就绪状态:
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。 -
运行状态:
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。 -
阻塞状态:
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:- 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
- 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
- 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
-
死亡状态:
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
13、产生死锁的四个必要条件:
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
11、练习题
- 三个线程t1、t2、t3。确保三个线程,t1执行完后t2执行,t2执行完后t3执行。执行顺序:t1->t2->t3
1.Semaphore实现,初始凭证0
2.Lock + condition实现
3.synchronized + wait + notifyAll实现
4.join
- 三个线程t1、t2、t3。确保三个线程,t1,t2执行完,t3再执行
1.CountDownLatch实现
2.join实现
3.synchronized + wait + notifyAll实现
4.Lock + condition实现
- 生产者消费者问题
1.synchronized + wait + notifyAll实现
2.Lock + condition实现
- 多线程循环顺序打印:ABABABABAB,线程A打印A,线程B打印B。
1.synchronized + wait + notifyAll实现
2.Lock + condition实现
- 多线程循环顺序打印:0102030405,线程0打印0,线程1打印奇数,线程2打印偶数
1.synchronized + wait + notifyAll实现
2.Lock + condition实现
12、基于ReentrantLock详解AQS
https://blog.youkuaiyun.com/Linging_24/article/details/134961383