线程常见方法
start()
- 启动一个新线程,在新的线程中运行run方法的代码
- start方法只是让线程进入就绪状态,里面的代码不一定立刻执行(CPU的时间片还没有分给它),每个线程的对象的start方法只能调用一次,调用多次会出现IllegalThreadStateException异常
sleep()
- 调用sleep方法会让当前线程从running进入到Time waiting状态(阻塞状态)
- 其他线程可以使用interrupt方法打断正在睡眠的线程,这时sleep方法会抛出InterruptedException
- 睡眠结束后的线程未必会立即得到执行
- 建议用TimeUnit的sleep代替Thread的sleep来获取到更好的可读性
yield()
- 调用yeild会让当前线程从Running进入到Runnable状态,然后调度执行其他线程
- 当前线程进入Runnable状态之后与其他线程竞争CPU资源,具体实现依赖于操作系统的任务调度器
下面通过一个代码来看
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
int count = 0;
@Override
public void run() {
for(;;){
log.debug("----- t1 > {}",count++);
}
}
},"t1");
Thread t2 = new Thread(new Runnable() {
int count = 0;
@Override
public void run() {
for(;;){
Thread.yield(); //线程t2让出时间片
log.debug(" ----- t2 > {}",count++);
}
}
},"t2");
t1.start();
t2.start();
}
上面的代码如果在线程t2中没有添加Thread.yield()方法,相当于两个线程公平竞争CPU时间片,所以从结果上来说差不多,但是如果在t2线程中添加了Thread.yield()方法,相当于t2线程让出了时间片,从结果上来看 相差挺大,但是从结果上也证实了上面的结论,1 让出时间片并不代表不会执行,2 一切取决于调度器的执行,正常情况会有影响
setPriority()
- 设置优先级
- Thread中定义了三种优先级 MIN_PRIORITY(1),NORM_PRIORITY(5),MAX_PRIORITY(10),如果没有指定优先级,默认优先级就是NORM_PRIORITY,数值越多,优先级越高
- 线程优先级会提示调度器有限调度该线程,但是它仅仅是一个提示,调度器可以忽略它
- 如果CPU比较忙,那么优先级高的线程会获得更多的时间片,如果CPU闲时,优先级几乎没有作用
借用上面的线程代码 调用下面的语句
t1.setPriority(Thread.MIN_PRIORITY);
t2.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();
从结果上看并不明显,这有可能是测试电脑是多核的缘故,因此并不明显
join()
- 这个语句的意思是等待线程结束
- 比如在main线程中调用t1.join()方法表示main线程要等t1线程执行完才能往下执行
- 如果同时执行多个线程的join()方法,各个方法之间应该是并行的,比如执行了t1.join(),t2.join(),其中t1线程sleep 1s,t2线程sleep 2s,等两个join执行完了花费的时间是2s,也就是说两个join是并行执行的
- join(long) 表示的有时限的等待线程结束,其中参数是毫秒
- 如果join(1.5s),而t1线程sleep 2s,则main线程在等待1.5s之后便不再等待
- 如果join(3s),而t1线程sleep 2s,则main线程在t1线程执行完之后便不再继续等待
interrupt
- interrupt 是打断线程
- 如果是打断sleep,join,wait等阻塞线程,不会重置打断标志,Thread类有一个isInterrupt方法 返回boolean类型,这个就是打断标志,默认是false,当打断这些阻塞线程时,这个打断标志还是false,这也是当调用这些方法时,需要try catch 住打断异常
- 如果打断的是正常线程,isInterrupt会被重置为true
- 哪个线程调用interrupt,打断的是哪个线程
- Thread 还有一个静态方法 interrupted 这个方法返回的也是boolean,表示线程是否被打断,它与isInterrupted的区别是调用isInterrupted之后打断标注并不会被清除,但是interrupted会清除打断标记,也就是说第一次调用返回的是true的话,下次调用就已经被清除掉了返回的是false
- interrupt 打断park线程,LockSupport.park()方法可以让线程停下来,可以通过interrupt来打断,通过interrupt打断park之后,打断标志为true,当打断标志为true时,后续再调用LockSupport.park并不会让线程停下来,可以通过调用Thread.interrupted方法清除掉打断标志
park&unPark
- park&unPark是LockSupport的静态方法
- 每个线程都会关联到LockSupport
- LockSupport里面有_count用来记录能否暂停线程,里面只能是1或者0,默认是0,当执行park方法时,会检查_count的值,如果是0,就暂停当前线程,如果是1就不会暂停当前线程,但是会把1变成0,当调用unPark(thread)的时候,会把_count变为1
- 如果park被打断,之后无论调用多少次park,都不会暂停线程了,这也就是说明对线程的暂停不仅取决于_count这个变量,还取决于打断标记
- park&unPark与wait¬ify有许多相似之处,但是也有不同的地方
- park&unPark不需要持有锁,wait¬ify需要持有锁
- unPark提前调用会影响到下一次的Park状态,而notify的先于wait调用好像并没有影响
- nofity的通知唤醒是随机的或者是全部的,而unPark的唤醒是指定的线程
- 两者之间的阻塞机制不一样 使用nofityAll 并不会唤醒被Park的线程
wait&nofity
- wait¬ify是Object上的方法
- 如果使用wait¬ify的方法必须首先获取到该Obj上的锁,这个相当于持有了锁如果调用该对象的wait方法,则线程会进入到Monitor的WaitSet队列中去(此时线程状态是TIME_wait),如果被唤醒会进入到EntrySet中等待调度(线程状态是BLOCKED)
- wait()方法有个重载方法wait(n)表示等待n毫秒之后就不再等待了,而wait()自身相当于一只等待直到被唤醒
- notify方法是在WaitSet中随机唤醒一个线程,nofityAll是唤醒WaitSet上的所有线程
- wait和sleep的方法都是进入到TIME_WAITING状态,但是它们之间有区别
- wait是Obj的方法,调用需要持有该Obj的锁
- Sleep是Thread的静态方法,并不需要持有锁
- 调用了wait方法之后会释放锁,sleep并不会释放锁
- 使用wait&noify需要注意虚假唤醒的情况,也就是WaitSet中有多个线程,如果有一个线程t3本来是想唤醒t2,线程结果却唤醒了t1线程,t1线程在被唤醒之后发现自身条件不满足,从而可能会造成逻辑上的错误,正确的做法是
Object object = new Object();
synchronized(object){
while(条件判断){
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//do something
}
}
另外的线程使用notifyall()
守护线程
- 当一个线程通过setDaemon(true)时就设置为守护线程了
- 默认情况下,java进程需要等待所有线程都运行结束,进程才会结束
- 如果非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束
- 垃圾回收器线程就是一种守护线程
线程状态
从CPU层面上讲,是五种状态
- 初始状态:仅是从语言层面创建创建了线程对象,还未与操作系统线程关联
- 可运行状态:指该线程已经被创建了(与操作系统线程关联),可以由CPU调度
- 运行状态:指的是获取了CPU时间片运行中的状态
- 当CPU时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换
- 阻塞状态:
- 如果调用了阻塞API,如BIO读写文件,这时该线程不会用到CPU,会导致线程上下文切换,进入【阻塞状态】
- 等BIO操作完毕,会由操作系统唤醒阻塞线程,转换至【可运行状态】
- 与【可运行状态】的区别是,对【阻塞状态】的线程来说只要他们一直不唤醒,调度器就一直不会调度它们
- 终止状态:表示线程已经执行完毕,生命周期已经结束,不会再转换为其他状态
从Java API层面上讲,是六种状态,这是Thread.State枚举定义的
- NEW:线程刚被创建,但是还没有调用start()方法
- RUNNABLE:当调用了start()方法之后,进入该状态
- 值得注意的是Java API 层面的RUNNABLE状态涵盖了操作系统的【可运行状态】,【运行状态】和【阻塞状态】,由于BIO导致的线程阻塞在Java里无法区分,仍然认为是可运行的
- BLOCKED
- WAITING
- TIMED_WAITING
- TERMINATED 表示线程代码运行结束
线程状态之间的切换:
【情况1】 : NEW --> RUNNABLE :当调用了t.start()
-
【情况2】 : RUNNABLE <--> WAITING: 线程用synchronized(obj)获取对象锁后
- 调用了obj.wait()方法时,线程从RUNNABLE -->WAITING
- 调用了obj.notify,obj.notifyAll,t.interrupt()方法时
a. 如果竞争锁失败,从WAITING -->BLOCKED
b. 如果竞争锁成功,从WAITING -->RUNNABLE
-
【情况3】 : RUNNABLE <--> WAITING
- 当线程调用t.join()方法时,当前线程从RUNNABLE -->WAITING(当前线程在t线程对象的Monitor上等待)
- t线程运行结束或者调用当前线程的interrupt(),当前线程从WAITING --> RUNNABLE
-
【情况4】 : RUNNABLE <-->WAITING
- 当前线程调用LockSupport.park()方法时,会让当前线程从RUNNABLE --> WAITING
- 调用LockSupport.unpark(目标线程)或者调用了线程的interrupt()方法会让目标线程 WAITING --> RUNNABLE
-
【情况5】 : RUNNABLE <--> TIMED_WAITING t线程调用了synchronized(obj)获得对象锁后
- 调用obj.wait(long n)方法时,t线程从RUNNABLE --> TIMED_WAITING
- t线程等待时间超过了n毫秒,或调用了obj.notify,obj.notifyAll,t.interrupt()方法时时
a. 竞争锁成功,t线程从TIMED_WAITING --> RUNNABLE
b. 竞争锁失败,t线程从TIMED_WAITING --> BLOCKED
-
【情况6】 : RUNNABLE <-->TIMED_WAITING
- 当前线程调用了t.join(long n)方法时,当前线程从RUNNABLE --> TIMED_WAITING(注意当前线程是在t线程对象的Monitor等待)
- 当前线程等待时间超过n毫秒或t线程运行结束或调用了当前线程的interrupt()方法时,当前线程从TIMED_WAITING -->RUNNABLE
-
【情况7】 : RUNNABLE <--> TIMED_WAITING
- 当前线程调用Thread.sleep(long n),当前线程会从RUNNABLE --> TIMED_WAITING
- 当前线程等待时间超过n毫秒,当前线程从TIMD_WAITING --> RUNNABLE
-
【情况8】 : RUNNABLE <--> TIMED_WAITING
- 当前线程调用了LockSupport.parkNanos(long nanos)或者LockSupport.parkUntil(long millis),当前线程从RUNNABLE -->TIMED_WAITING
- 调用LockSupport.unpark(目标线程)或调用了线程的interrupt()或者等待超时,会让目标线程从TIMED_WAITING -->RUNNABLE
-
【情况9】 : RUNNABLE <--> BLOCKED
- t线程用synchronized(obj)获取对象锁时如果竞争失败,从RUNNABLE-->BLOCKED
- 持有obj锁对象的代码块执行完毕,会唤醒该对象上所有BLOCKED的线程重新竞争,如果竞争成功 从BLOCKED --> RUNNABLE,其他失败的仍然是BLOCKED
-
【情况10】 : RUNNABLE --> TERMINATED
- 当前线程所有代码运行完毕,进入TERMINATED