赞
踩
Android之多线程,包括线程、Java同步问题、阻塞队列、线程池、AsyncTask、HandlerThread、IntentService等内容。
本文是我一点点归纳总结的干货,但是难免有疏忽和遗漏,希望不吝赐教。
转载请注明链接:https://blog.csdn.net/feather_wch/article/details/79132183
有帮助的话请点个赞!万分感谢!
版本号: 2018/9/18(10:10)
1、什么是进程
- 系统分配资源的最小单位
- 进程就是程序运行的实体
2、什么是线程
- 系统调度的最小单位
- 一个进程中可以包含多个线程
- 线程拥有各自的计数器、堆栈和局部变量等属性,能够访问共享的内存变量
3、线程的好处
- 比进程的资源消耗要小,效率要更高
- 多线程的并发性能减少程序的响应时间
- 多线程能简化程序的结构,使程序便于理解和维护
4、线程的状态有哪些?
状态 | 解释 | 备注 |
---|---|---|
New | 新创建状态 | 线程已被创建,还未调用start,做一些准备工作 |
Runnable | 可运行状态 | start之后进入,Runnable线程可能在运行也可能没有在运行,取决于系统分配的时间 |
Blocked | 阻塞状态 | 线程被锁阻塞,暂时不活动 |
Waiting | 等待状态 | 线程不运行任何代码,消耗最少的资源,直至调度器激活该线程 |
Timed Waiting | 超时等待状态 | 与Waiting 不同在于,可以在指定时间内返回 |
Terminated | 终止状态 | 当前线程执行完毕:可能是run运行结束,或者出现未能捕获的异常导致线程终止 |
5、线程如何从新建状态
进入可运行状态
?
Thread.start()
6、线程如何从可运行状态
到阻塞状态
- 线程在
请求锁
的时候会进入阻塞状态
- 线程一旦
得到锁
会返回到可运行状态
备注:
如下题目中的
Object.wait()
是指具体对象调用wait等方法---someObject.wait()
,Thread.join
是指具体线程调用该方法—childThread.join()
7、线程如何从可运行状态
切换到等待状态
- 进入等待:
Object.wait()---当前线程进入等待状态(当前线程需要已经获得过锁,且调用后会失去锁)、Thread.join()---父线程会等待子线程
- 退出:
Object.notify()和Object.notifyAll()
8、线程如何从可运行状态
切换到超时等待状态
- 进入:
Thread.sleep(long)、Thread.join(long)---让父线程等待子线程,子线程结束后父线程才继续执行、Object.wait(long)
- 退出:
Object.notify()、Object.notifyAll()
或者超时退出
9、线程如何从可运行状态
切换到终止状态
- 执行完毕
- 异常退出
10、Object.notify()、Object.notifyAll()之间的区别
Object.notify()
: 随机唤醒一个wait线程
,调用该方法后只有一个线程会由等待池
进入锁池
。
Object.notifyAll()
: 会将对象等待池
中的所有线程都进入锁池
,进行竞争。竞争到的线程会继续执行,在释放掉对象锁
后,锁池中的线程会继续开始竞争。(进入到锁池的线程,不会再进入等待池)
11、等待池和锁池是什么?
等待池
:线程调用对象的wait
方法后,会释放该对象的锁,然后进入到该对象的等待池中
。锁池
:线程想要获得对象的锁,但是此时锁已经被其他线程拥有,这些线程就会进入到该对象的锁池
中。
12、创建线程的三种方法
- 继承Thread,重写run方法
- 实现Runnable接口,并实现该接口的run方法
- 实现Callable接口(Executor框架中的内容,优势是能在任务结束后提供一个返回值,Runnable无法这样做),重写call方法。
- 推荐第二种
Runnable接口
的方法,因为继承Thread没有必要。
13、终止线程的两种方法
- 调用
Thread.interrupted()
设置中断标志位,并通过Thread.currentThread().isInterrupted()
检查标志位。缺点:被中断的线程不一定会终止- 在
run()
方法中设置boolean标志位(需要volatile
修饰为易变变量):条件满足时跳出循环,run运行结束,thread安全终止
14、实现Callable接口创建多线程
1-实现Callable接口,重写run方法。
2-使用FutureTask进行包装,并且执行
// 1、实现Callable接口,并用futureTask包装。
FutureTask<Integer> futureTask = new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
// TODO sth,并返回结果。
Integer result = 1;
return result;
}
});
// 2、开启线程并且执行任务
Thread thread = new Thread(futureTask);
thread.start();
// 3、获取到线程执行的返回值
Integer result = futureTask.get();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
1、重入锁是什么?(3)
- 重入锁ReentrantLock在Java SE 5.0引入
- 该锁支持一个线程对资源的重复加锁
- 一个线程在锁住锁对象后,其他任何线程都无法进入Lock语句
val mLock = ReentrantLock()
mLock.lock()
try {
//需要同步的操作
}finally {
mLock.unlock() //finally中进行解锁,避免死锁问题
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
2、可重入锁的用途?
阻塞队列
就是使用ReentrantLock
实现的。
3、条件对象/条件变量的作用(4)
- 用于管理那些获得锁却因部分条件不满足而无法正常工作的线程
- 可以通过
newCondition
获得锁lock
的条件变量(和ReentrantLock配合使用
)- 条件对象调用
await
方法,当前线程就会阻塞并且放弃该锁await
线程会进入阻塞状态
,直到另一个线程,调用同一条件对象的signalAll()
方法,之后等待的所有线程通过竞争条件去抢锁
//1. 可重入锁 val mLock = ReentrantLock() mLock.lock() //2. 条件变量 val condition = mLock.newCondition() try { while(条件不满足){ //3. await进入Block状态 condition.await() } //4. 条件满足方会进行后续操作 //... //5. 操作完成后调用同一条件变量的signalAll去激活等待该条件的线程 condition.signalAll() }finally { mLock.unlock() //finally中进行解锁,避免死锁问题 }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
4、synchronized同步方法(4)
Lock
和condition
提供了高度的锁定控制,然而大多数情况下不需要这样麻烦- 从Java 1.0开始,每个对象都有一个内部锁
- 当一个方法使用
synchronized
修饰,意味着线程必须获得内部锁,才能调用该方法
synchronized public void doSth() throws InterruptedException{
//1. 条件不满足,进入Block状态
while(条件不满足){
wait();
}
//2. 条件满足方会进行后续操作
//...
//3. 解除该锁,并通知所有阻塞的线程
notifyAll();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 备注:Kotlin学的不深,暂时没找到Kotlin中同步的方法,就用Java实现
5、同步代码块的使用(1)和问题(2)
- java中可以通过给一个Object对象上锁,来使用代码块
- 同步代码块非常脆弱不推荐
- 一般实现同步,最好使用
java.util.concurrent
包下提供的类,例如阻塞队列
Object object = new Object();
synchronized (object){
//进行处理, 不推荐使用
}
- 1
- 2
- 3
- 4
6、synchronized方法和synchronized同步代码块的区别?
用synchronized修饰符
修饰的方法就是同步方法
synchronized代码块
需要一个对象锁
7、死锁是什么?
死锁
是指两个或者两个以上线程/进程
进行资源竞争时出现了阻塞现象,如果没有外力帮助,它们都将无法继续工作下去,此时系统处于死锁状态
。
8、可能出现死锁的场景
可重入锁ReentrantLock
在mLock.lock()
后如果出现异常,但是没有在try-catch
的finally
中没有执行mLock.unLock()
就会导致死锁。notify
比notifyAll
更容易出现死锁
1、Java的堆内存是什么?(2)
- 堆内存用于存储
对象实例
- 堆内存被所有线程所共享: 会存在内存可见性问题
2、 Java中的局部变量、方法定义的参数是否会被线程所共享?
- 局部变量、方法定义的参数则不会在线程间共享:不存在内存可见性问题,也不会受到内存模型影响,
3、Java内存模型的作用
- Java内存模型控制线程之间的通信,
决定了
一个线程对共享内存的写入何时对另一个线程可见。定义了线程和主存之间的抽象关系:
- 线程之间的共享变量存储在
主存
中,每个线程都有一个私有的本地内存
,本地内存中存储了该该线程贡献变量的副本,本地内存是Java内存模型中的抽象概念
,实际上并不存在,覆盖了缓存、写缓存区、寄存器
等区域。
4、Java内存模型图
5、线程间共享变量的通信需要满足的步骤?(2)
A、B线程之间数据通信,需要满足两个步骤:
- 第一步线程A将本地更新过的共享数据刷新到主存中;
- 第二步线程B到主存中读取已经刷新的最新共享数据
6、可见性是什么?
- 可见性是指线程修改的状态能否立即对另一个线程可见
volatile
修饰的变量,在发生变化后会立即更新到主存,已保证共享数据的可见性
7、volatile关键字的作用
- 能保证有序性:禁止指令重新排序,之前的指令不会在volatile之后执行,之后指令也不会在之前执行
- 保证可见性:更新的数据立即可见
- 不保证原子性
8、有序性是什么?(2)
- Java中
编译器和处理器
能对指令的顺序重新排序,可不会影响单个线程执行的正确性,却无法保证多线程并发的正确性。保证多线程并发的正确性
就需要保证有序性
9、如何能保证有序性?(2)
1.
volatile
能保证有序性
2.synchronized和Lock
也能保证有序性
10、原子性是什么?
- 对基本数据类型变量的赋值和读取是原子性操作-要么不执行,要么不会被中断。
x = 3; //原子操作
y = x; //非原子操作:复制,并且存储
x++; //非原子操作: 读取x,自加,存储
- 1
- 2
- 3
11、如何保证操作的原子性?
java,util.concurrent.atomic
包中很多类使用高效的机器级别指令来保证操作的原子性AtomicInteger
的incrementAndGet/decrementAndGet()
提供原子性自加/自减-可以作为共享计数器而无需同步AtomicBoolean、AtomicLong、AtomicReference
等类也都是原子性操作- 原子性操作类应该由开发并发工具的程序员使用,而不是应用程序员使用
12、使用volatile的典型场景
- 状态标志:如线程run方法中通过标志位判断是否终止线程,就比使用synchonized要简单和高效(
通过synchronized也可以实现,但是该场景使用volatile性能更高
)- 双重检查模式(DCL): 应用于单例模式的getInstance保证实例唯一。DLC资源利用率高,第一次加载反应稍慢,在高并发情况下有一定缺陷
13、部分场景下使用volatile取代synchronized的要点
synchronized
能提供同步保护,却会影响性能。一定场景下可以用volatile
替换。volatile
无法保证原子性,必须具备两个条件才可以替换- 条件1:对变量的写操作不依赖于当前值(不能自增、自减)
- 条件2:该变量没有包含在具有其他变量的不等式中(例如,volatile a,b, 不等式a<b无法保证线程安全)
- 总结:变量独立于其他变量,也独立于自己本身之前的数值
1、什么是阻塞队列
- 阻塞队列常应用于生产者-消费者模型
- 阻塞队列需要满足1:队列中没有数据时,消费者端的所有线程全部自动阻塞(挂起)
- 阻塞队列需要满足2:队列中填满数据时,生产者端的所有线程都自动阻塞
2、阻塞队列(BlockingQueue)核心方法
- 放入数据:
- offer(object),可以存入,返回true;不可以存入,返回false。该方法不会阻塞当前线程
- put(Object), 阻塞队列有空间,则存入;没有空间,当前线程阻塞,直至阻塞队列有空间存放数据。
- 获取数据:
- poll(time):从阻塞队列中将首位对象取出。若不能取出,再等待time的时间。取不到就返回null
- take():取走队列中首位数据。若队列为空,则当前线程阻塞,直到队列中有数据,并且返回该数据。
- drainTo(): 一次性取走所有可用数据。无需多次加锁操作,提高效率。
3、Java中7种阻塞队列的要点
种类 | 特点 | 备注 |
---|---|---|
ArrayBlockingQueue | 数组组成的有界阻塞队列 | 默认不保证线程公平地访问队列 |
LinkedBlockingQueue | 链表组成的有界阻塞队列 | 若构造时不指定队列缓存区大小,默认无穷大。一旦生产速度>消费速度,会导致内存耗尽 |
PriorityBlockingQueue | 支持优先级排序的无界阻塞队列—默认升序排列 | 能通过compareTo 方法和构造参数comparator 对元素排序,但无法保证同级元素的顺序 |
DelayQueue | 延时获取元素的无界阻塞队列 | 每个元素必须实现Delayed接口,创建时指定元素到期时间,元素到期后才能取出 |
SynchronousQueue | 不存储元素的阻塞队列 | |
LinkedTransferQueue | 链表存储的无界阻塞队列 | |
LinkedBlockingDeque | 链表存储的双向阻塞队列 | 可以从两端同时插入和删除,减少一半竞争 |
4、阻塞队列ArrayBlockingQueue实现原理
- 内部维护一个Object类型的数组
- lock所采用的可重入锁(ReentrantLock)
5、ArrayBlockingQueue的put()源码解析和要点
/** * 存放数据 * 1-有空间存放,就直接存入数据 * 2-没有空间存放,当前线程阻塞到有多余空间,再存入 */ public void put(E e) throws InterruptedException { Objects.requireNonNull(e); final ReentrantLock lock = this.lock; //1. 获取锁,并且是可中断的锁 lock.lockInterruptibly(); try { //2. 判断数组是否已满 while (count == items.length){ //3. 已满,条件变量(notFull)阻塞当前线程 notFull.await(); } //4. 未满,将元素插入数组 enqueue(e); } finally { //5. 最后解锁 lock.unlock(); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
6、ArrayBlockingQueue的enqueue()源码解析和要点
private void enqueue(E x) {
//1. 在putIndex下标处放入元素
final Object[] items = this.items;
items[putIndex] = x;
//2. 插入后若已经到数组尾部,则从头部开始(puIndex = 0)
if (++putIndex == items.length) putIndex = 0;
count++;
//3. 解锁条件变量-notEmpty-唤醒正在等待获取元素的线程
notEmpty.signal();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
7、ArrayBlockingQueue的take()源码解析和要点
/** * 取走队列中首位元素。 * 1-队列为空,当前线程阻塞,直到队列中有数据,并且返回该数据 */ public E take() throws InterruptedException { final ReentrantLock lock = this.lock; //1. 获得锁-可中断锁 lock.lockInterruptibly(); try { //2. 若队列为空,阻塞,直到队列中有数据 while (count == 0) notEmpty.await(); //3. 队列不为空,获取数据 return dequeue(); } finally { //4. 解锁 lock.unlock(); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
8、ArrayBlockingQueue的dequeue()源码解析和要点
/** * 取出元素,仅仅在获得锁时被调用 */ private E dequeue() { //1. 取出元素 final Object[] items = this.items; @SuppressWarnings("unchecked") E x = (E) items[takeIndex]; items[takeIndex] = null; if (++takeIndex == items.length) takeIndex = 0; count--; if (itrs != null) itrs.elementDequeued(); //2. 激活等待notFull条件的线程 notFull.signal(); return x; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
9、非阻塞队列实现消费者模型源码:
public class Main { private int queueSize = 10; private PriorityQueue<Integer> queue = new PriorityQueue(queueSize); public static void main(String args[]){ Main main = new Main(); Consumer consumer = main.new Consumer(); consumer.start(); Producer producer = main.new Producer(); producer.start(); } class Consumer extends Thread{ @Override public void run() { while(true){ synchronized (queue){ while(queue.size() == 0){ System.out.println("仓库中没有产品"); try { queue.wait(); } catch (InterruptedException e) { e.printStackTrace(); queue.notify(); } } Integer product = queue.poll(); System.out.println("消耗产品:"+product); queue.notify(); } } } } class Producer extends Thread{ int product = 0; @Override public void run() { while(true){ synchronized (queue){ while(queue.size() == queueSize){ System.out.println("仓库已满"); try { queue.wait(); } catch (InterruptedException e) { e.printStackTrace(); queue.notify(); } } queue.offer(product); System.out.println("生产产品:"+product); product++; queue.notify(); } } } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
10、阻塞队列实现消费者模型(核心代码):
使用阻塞队列就不需要处理锁,实现简单
private int queueSize = 10; private ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue(queueSize); class Consumer extends Thread{ @Override public void run() { while(true){ int product = -1; try { product = queue.take(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("消耗产品:"+product); } } } class Producer extends Thread{ int product = 0; @Override public void run() { while(true){ try { queue.put(product); System.out.println("生产产品:"+product); product++; } catch (InterruptedException e) { e.printStackTrace(); } } } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
1、为什么需要线程池?
- 每个线程的创建/销毁都有一定开销,通过维护一定量的线程就可以减少这些开销。
- 线程
各自为政
,难以控制管理
。
2、Executor框架的作用?
- Java 1.5开始提供
Executor
框架用于把任务提交和任务处理解耦- 任务的提交:交给
Runnable
、Callable
处理。- 任务的处理:交给
Executor
框架。Executor
框架核心是ThreadPoolExecutor
-也就是线程池的核心实现类
3、ThreadPoolExecutor的作用
ThreadPoolExecutor
可以创建一个线程池
4、ThreadPoolExecutor构造方法的七个参数的作用?
线程池参数 | 含义 | 作用 |
---|---|---|
corePoolSize | 核心线程数 | 默认线程数为0,若当前运行的线程数少于该值,则会创建新线程处理任务 |
maximumPoolSize | 线程池允许创建的最大线程数 | 若任务队列已满且线程数少于该值,则仍会创建新线程 |
keepAliveTime | 非核心线程闲置的超时时间 | 线程闲置时间若超过该时间则回收该线程。若任务很多,且执行时间都很短,提高该值可以提高线程利用率。allowCoreThreadTimeOut 属性为True时,该值也会用于核心线程 |
TimeUnit | keepAliveTime的时间单位 | 可以为DAYS\HOURS\MINUTES\SECONDS\MILLISECONDS |
workQueue | 任务队列 | 若当前线程数>corePoolSize,将任务添加到任务队列。为BlockingQueue |
ThreadFactory | 线程工厂 | 可以用于给每个创建的线程设置名字。一般无需使用 |
RejectedExecutionHandler | 饱和策略 | 当任务队列和线程池都已满时,采用何种策略。默认是AbordPolicy -无法处理任务时,抛出异常 |
5、饱和策略有哪些?(4)
AbordPolicy
:无法处理任务时,抛出异常CallerRunsPolicy
:调用者所在线程处理任务;DiscardPolicy
:将不能执行的任务删除;DiscardOldestPolicy
:丢弃队列最老的任务,并执行当前任务
6、线程池的处理流程和原理
7、进入任务队列中的任务会被如何处理?
- 任务队列中的任务,会在有核心/非核心线程空闲时,被取走并处理
8、线程池的4种常用种类
- FixedThreadPool: 可重用固定线程数的线程池
- CachedThreadPool: 根据需要创建线程的线程池
- SingleThreadExecutor:使用单个工作线程的线程池
- ScheduledThreadPool:能实现定时和周期性任务的线程池
9、FixedThreadPool的特点
只有核心线程且数量固定(通过够早的参数指定)
,没有非核心线程数
(corePoolSize = maximumPoolSize)- 多余的线程会被立即终止(keepAliveTime = 0L)
- 任务队列使用
无界阻塞队列(LinkedBlockingDeque)
- 思路:当线程数达到corePoolSize时,就将任务存储在任务队列中,且等待空闲线程去执行
10、CachedThreadPool
没有核心线程
(corePoolSize = 0)非核心线程无限
(maximumPoolSize = Integer.MAX_VALUE)- 空闲线程等待新任务时间为60s(keepAliveTime = 60L)
- 阻塞队列采用
SynchronousQueue
-不存储元素的阻塞队列,每个线程插入操作必须等待另一个线程的移除操作,反之亦然。- 思路:每次提交的任务都会立即有线程去执行。线程一旦空闲,会等待60s。
- 特点:适合大量需要立即处理,并且耗时较少的任务。
11、SingleThreadPool
只有一个核心线程
,没有非核心线程
(corePoolSize=maximumPoolSize=1)- 其余参数和
FixedThreadPool
一致- 流程:新提交的任务,若没有核心线程则创建并执行该任务,若核心线程正在处理任务则将新任务提交至阻塞队列中,等待处理。
- 特点:保证所有任务都在一个线程中按顺序处理。
12、ScheduledThreadPool(定时和周期性任务)
- 核心线程数由corePoolSize决定
- 阻塞队列使用
无界
的DelayedWorkQueue
,因此没有非核心线程(第二个参数无效0)- 每个要执行的任务被包装成
ScheduledFutureTask
,放入任务队列。等待线程执行- DelayedWorkQueue会对任务进行排序,最需要执行的放在最前方。
- 不同:任务执行后,会更改
ScheduledFutureTask
的time变量为下次执行的时间,并放回到队列中
13、新建线程时,必须通过线程池提供(AsyncTask或者ThreadPoolExecutor), 不应该在应用中自行显式创建线程。
14、线程池创建的三种方法?
- Executors:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
- 1
- ThreadPoolExecutor:
ExecutorService executorService = new ThreadPoolExecutor(xxx);
- 1
- 其他常见线程池如:ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor mScheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(CORE_POOL_SIZE, sThreadFactory);
- 1
15、线程池的创建为什么不能使用Executors创建?
FixedThreadPool
和SingleThreadPool
:允许的请求队列长度为Integer.MAX_VALUE
,可能会堆积大量的请求,导致OOM。CachedThreadPool
和ScheduledThreadPool
:允许的创建线程数量为Integer.MAX_VALUE
,可能会导致创建大量的线程,导致OOM。
16、ScheduledThreadPool会出现创建大量线程的问题反思
ScheduledThreadPool
构造方法里面的确将线程最大数设置为Integer.MAX
- 但是使用的
阻塞队列
是DelayedWorkQueue
, 该队列是无界队列(会自动扩容)
根据线程池的处理流程
,永远不会出现任务数 >= 核心线程数 + 任务队列总数
的情况,因此根本是用不到 线程数最大为Integer.MAX这个参数
1、Thread的run()和start()的区别
start()
会让线程去排队(处于就绪状态),之后会调用run()
run()
是线程需要执行的内容
2、Synchronized三种应用的锁是什么?
- Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
- 普通同步方法,锁是当前实例对象
- 静态同步方法,锁是当前类的class对象
- 同步方法块,锁是括号里面的对象
3、现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行?
考察对Join是否熟悉:父线程会等待子线程运行结束
Thread thread3 = new Thread(new Runnable() { @Override public void run() { /** * T2:T3等待T2执行完毕 */ Thread thread2 = new Thread(new Runnable() { @Override public void run() { /** * T1:T2等待T1执行完毕 */ Thread thread1 = new Thread(new Runnable() { @Override public void run() { System.out.println("Thread 1 stopped"); } }); thread1.start(); // T2等待T1执行完毕 thread1.join(); System.out.println("Thread 2 stopped"); } }); thread2.start(); //T3等待T1执行完毕 thread2.join(); System.out.println("Thread 3 stopped"); } }); thread3.start();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
4、在Java中Lock接口比synchronized块的优势是什么?你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它?
5、在java中wait和sleep方法的不同?
6、用Java实现阻塞队列。
7、用Java写代码来解决生产者——消费者问题。
8、用Java编程一个会导致死锁的程序,你将怎么解决?
9、什么是原子操作,Java中的原子操作是什么?如何同步一个原子操作?
10、Java中的volatile关键是什么作用?怎样使用它?在Java中它跟synchronized方法有什么不同?
11、什么是竞争条件?你怎样发现和解决竞争?
12、你将如何使用thread dump?你将如何分析Thread dump?
13、为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法?
14、Java中你怎样唤醒一个阻塞的线程?
15、在Java中CycliBarriar和CountdownLatch有什么区别?
16、什么是不可变对象,它对写并发应用有什么帮助?
17、你在多线程环境中遇到的共同的问题是什么?你是怎么解决它的?
18、在java中绿色线程和本地线程区别?
19、线程与进程的区别?
20、什么是多线程中的上下文切换?
21、死锁与活锁的区别,死锁与馅饼的区别?
22、Java中用到的线程调度算法是什么?
23、在Java中什么是线程调度?
24、在线程中你怎么处理不可捕捉异常?
25、什么是线程组,为什么在Java中不推荐使用?
26、为什么使用Executor框架比使用应用创建和管理线程好?
27、在Java中Executor和Executors的区别?
28、如何在Windows和Linux上查找哪个线程使用的CPU时间最长?
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。