什么是进程?
进程是程序(应用程序,如:微信
)运行资源分配的最小单位
什么是线程?
线程是CPU
调度的最小单位,必须依赖于进程存在
CPU时间片轮转机制?
时间片轮转调度是一种最古老、最简单、最公平且使用最广的算法,又称为RR
调度.
作用
让每个进程分配一个时间段,称作他(进程
)的时间片,即该进程允许运行的时间.(CPU时间切片
)
启动线程有几种方式?
- new Thread().start();
- implements Runnable 将mRunnable放在new Thread(
mRunnable
).start();中去执行
Thread和Runnable之间的区别是什么?
Thread是对线程的抽象
Runnable是对业务/任务的抽象
Java中如何停止线程?
不建议使用stop
(停止
)方法,因为这个方法执行后没有释放线程所占用的资源,同样的还有suspend
(挂起
)和resume
(唤醒
)方法.
应当使用interrupt(中断)方法
下面是两个判断是否被中断的方法:
isInterrupted
判断完成后,不会更改interrupt的值,仍是true
interrupted
判断完成后,会重置interrupt的值为false
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
public boolean isInterrupted() {
return this.isInterrupted(false);
}
使用方式:
可以在线程调用interrupt方法后,在Thread的run方法中判断线程是否中断,或者在Runnable的run方法中判断线程是否中断(Runnable
的run
方法中通过Thread.currentThread()
获取当前线程);线程中断后可以做一些资源释放的工作.
JDK中线程是协作式的,而不是抢占式的
深入理解Thread的start方法和run方法
- start方法调用后,线程的run方法中获取到的线程就是我们当前定义的线程;
- 不调用start方法,直接调用线程的run方法,那么在run方法中获取到的线程就是main线程
线程的join方法(面试题:如何保证几个线程的顺序执行
)
一个线程a启动后,另外一个线程b也启动了,如果正常情况下,应该是a线程执行完成后再执行b线程;但是如果线程b使用join方法,那么线程的执行顺序就会发生变化,线程b执行完成后才会执行线程a.
守护线程Daemmon
- 可以通过线程的
setDaemon(true)
方法将当前用户线程
更改为守护线程
; - 如果用户线程执行完成后,守护线程也会随之停止;
- 并且守护线程的run方法中,try/finally语法中的
finally
逻辑中的代码不一定会执行.
线程间共享之Synchronized内置锁
- 同步块 (
synchronized(object)参数是对象
) - 方法 (
同样是表示当前类的对象
)
对象锁和类锁
对象锁是作用在类的对象身上的,如非静态方法加锁
类锁是作用在当前类加载到虚拟机中class对象上的,如静态方法加锁
volatile关键字
最轻量级的同步机制,保证线程间的可见性
;
一写多读
的作用,一个线程修改,其他线程都能获取到修改后的值
可见性案例:
如果主线程中将一个变量的值进行更改,在子线程中去打印这个值,这个值不会改变;
如果将这个变量声明为volatile
,在主线程中更改他的值,那么在子线程去打印这个值,此时这个变量的值,是更改后的值.
ThreadLocal
每个线程Thread
都有一个变量的副本ThreadLocal.ThreadLocalMap
,这个map
中有一个Entry[]
数组,Entry
对象是以ThreadLocal
作为key
,传入的对象作为value
的键值对;
我们存入的数据就存放在这个Entry[]
数组中,从而实现了多个线程操作同一个ThreadLocal
对象的线程安全性,因为每个线程都是在操作当前线程副本 ThreadLocal.ThreadLocalMap
中的Entry
数组.
说一说ThreadLocal
发生内存泄漏?
因为ThreadLocal
使用的弱引用WeakReference
,所以有GC
的时候,当前ThreadLocal
对象就会被回收,从而导致了当前Entry
对象中key
为null
,value
依旧是之前存入的对象,Entry
作为ThreadLocal.ThreadLocalMap
的Entry[]
数组中的一个元素,他持有了该ThreadLocalMap
对象,而该ThreadLocalMap
对象同时也持有了该Thread
线程对象,所以在ThreadLocal
被回收时,当前线程仍未被回收,从而导致了内存泄漏.
代码如下:
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
解决方案:
在每次使用完ThreadLocal
后,调用threadLocal.remove()
方法,将当前ThreadLocal
所在的Entry
对象从Entry[]
数组中移除;代码举例如下:
ThreadLocal<Handler> localVariable = new ThreadLocal<>();
localVariable.set(new Handler());
localVariable.remove();
ThreadLocal线程不安全?
static对象造成的多个线程操作同一个共享对象的引用,从而引发了线程不安全性
线程间的通知和等待,Object
类的wait
和notify/notifyAll
wait
wait使用方式,必须放在synchronized同步方法或者代码块中,wait方法代码执行完成后会立即释放锁;等待notify/notifyAll方法将当前wait方法唤醒后,才会重新去竞争锁资源,竞争到锁资源后,才会执行该方法的业务逻辑.notify/notifyAll
notify/notifyAll使用方式,必须放在synchronized同步方法或者代码块中,在业务逻辑执行完成后,再调用notify/notifyAll方法,此时将会唤醒wait等待的对象(让wait等待的线程去竞争锁
),当该同步方法或者是同步代码块
执行完成后才会释放锁.
使用方式
synchronize方法或代码块synchronize(object)中{
while(不满足条件){
对象.wait();
}
//满足条件,执行业务逻辑
}
synchronize方法或代码块synchronize(object)中{
//执行业务逻辑
//业务逻辑执行完成后
对象.notify();//对象.notifyAll();
}
yield(放弃)、sleep(休眠)、wait(等待)、notify(唤醒)比较?
- yield和sleep是线程
Thread
的方法,这两个方法在调用后不会释放当前锁占用的资源(需要等待同步方法或者代码块执行完成后才会释放
) - wait和notify是对象
Object
的方法,这两个方法在同步方法或同步代码块执行完成后会释放当前同步锁占用的资源
分而治之,Fork-Join(拆分-合并)思想,工作密取,
CountDownLatch
闭锁
countDown()
扣减 计数器减去1,直至值为0await()
等待 等待计数器是0
的时候执行该方法后面的业务逻辑
代码使用
CountDownLatch mCountDownLatch = new CountDownLatch(6);
//多个线程,线程数量小于等于6,一个线程可以多次调用countDown方法,当计数器减至0,会执行await方法后面的业务逻辑
//主线程或者是子线程执行countDown()
mCountDownLatch.countDown();//执行6次,计数器减至0,会执行await方法后面的业务逻辑
//多个线程调用await方法,在计数器减至0时同时执行await方法后面的业务逻辑
mCountDownLatch.await();
//await方法后面的业务逻辑
计数器和线程数不一定是相等的,计数器可以大于等于线程数,线程执行完成一个任务,就调用countDown()
方法,计数器减去1
;一个线程可以多次执行countDown()
方法,此时计数器就减去该方法执行的次数;别的主线程或子线程调用await()
方法,当计数器为0
的时候,这些等待的线程就会执行await()
方法后面的业务逻辑
CyclicBarrier(循环屏障)
await
这个方法调用一次计数器加1
,计数器达到构造函数中的int
值时,执行await
方法后面的业务逻辑和汇总线程中的业务逻辑
使用方式:
//在await调用次数为计数器4的时候,会第一次执行汇总线程CollectThread()中的业务逻辑,同时接着会执行await方法后面的业务逻辑;
//await调用次数为计数器8的时候,会第二次执行汇总线程CollectThread()中的业务逻辑,同时接着会执行await方法后面的业务逻辑,
//这就是CyclicBarrier中Cyclic循环的含义
CyclicBarrier mCyclicBarrier = new CyclicBarrier(4, new CollectThread());
mCyclicBarrier.await();
//TODO await方法后面的业务逻辑
相较于CountDownLatch
可以循环使用,CountDownLatch
在计数器为0
的时候,可以执行主线程或者子线程中await
方法后的业务逻辑,但是只能执行1
次;而CyclicBarrier
在调用await
方法后,在执行次数等于计数器值的时候,各个线程同样也会执行await
方法后面的业务逻辑;同时该API
还可以定义一个汇总线程
,用于处理等待的计数器值达到临界值时,在汇总线程中处理业务逻辑.
在await
方法达到计数器临界值
时,计数器的值会进行重置
,如果在后面重复调用await
方法,可以第2
次达到计数器临界值,循环执行汇总线程或者await
方法后面的业务逻辑.
Callable、Futrue和FutureTask
UseCallable useCallable = new UseCallable();
//用FutureTask包装Callable
FutureTask<Integer> futureTask = new FutureTask<>(useCallable);
//交给Thread去运行
new Thread(futureTask).start();
//futureTask拿到传入参数Callable(userCallable)的执行结果
futureTask.get();