一、线程状态图
线程状态图二、详解
1.可运行状态
包括:就绪态和运行中两种状态。
虽然调用了start()
方法,这个线程看起来开始运行了,但是不一定会运行,要看cpu有没有给你这个线程分配时间片。如果分配了,当前就是运行中状态;如果没有分配时间片,则处于一个就绪中的状态,只不过是在随时等待着cpu分配时间片。
查看某个线程的状态
在jdk文件夹下的-->bin目录下,运行:
jps -v
命令可以看到当前机器下运行的java程序,接着用jsack [程序号]
可以看到指定的程序中所有的线程的状态。
2.LockSupport
其中的一个park()是jdk底层的一个专门阻塞线程的方法。
3.等待状态和等待超时状态
- 区别在于调用wait方法或者join方法的时候有没有带时间数,如果带了时间数或者调用sleep方法,则进入等待超时状态。如果没有特定地去唤醒这个线程的话,这个线程超过指定的时间就会自动回到可运行状态。
- 而处于等待状态的线程,必须要专门的唤醒
notify()或者notifiAll()方法
,才能进入可运行状态。
4.阻塞
- 当我们用synchronized关键字时,线程会进入一个阻塞状态。表现上和等待状态很像,只不过它是等待某个内置锁而触发的。当有多个线程共同运行时。
- 实际开发中我们会用synchronized关键字对程序进行加锁,当有多个线程同时竞争这把锁的时候,在任意时刻,有且仅有一个线程能获取这把锁,其他的线程此时处于一种阻塞状态。当等待锁的线程获取到锁时,重新进入可运行状态。
5.Thread实现了Runnable接口
三、run()方法和start()方法
底层
start方法在同一线程中只能调用一次!!!
why?
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
- 可以看到,调用start方法时,会先判断threadStatus 是否为0,如果不为0,则会抛出一个
IllegalThreadStateException
异常。 -
同一个线程只能调用start()方法一次,多次调用会抛出java.lang.IllegalThreadStateException。启动一个线程,需要调用start()方法而不是run()方法。此时,当前线程会被添加到线程组中,进入就绪状态,等待线程调度器的调用,若获取到了资源,则能进入运行状态,run()方法只是线程体,即线程执行的内容,若没调用start()方法,run()方法只是一个普通的方法。
threadStatus
1.真正意义上的线程
ThreadRun threadRun = new ThreadRun();
这个new方法只是说我们在jdk的虚拟机中new出了一个Thread类的实例,还没有和操作系统中的线程真正挂钩,只有执行了start方法才真正启动了一个线程。这个start方法不能重复地调用。当线程获取到时间片之后,才会真正执行run方法。run方法只是一个业务方法,它可以被重复地执行,也可以被单独地调用。
什么意思呢?看如下代码
可以看到run方法本身只是一个实现业务逻辑的地方,真正和线程挂钩的方法还是start方法。
四、yield()方法
yield()用的很少,是一个原生方法,只是当前线程让出cpu的执行权,告诉操作系统,CPU我不用了。
/**
* A hint to the scheduler that the current thread is willing to yield
* its current use of a processor. The scheduler is free to ignore this
* hint.
*
* <p> Yield is a heuristic attempt to improve relative progression
* between threads that would otherwise over-utilise a CPU. Its use
* should be combined with detailed profiling and benchmarking to
* ensure that it actually has the desired effect.
*
* <p> It is rarely appropriate to use this method. It may be useful
* for debugging or testing purposes, where it may help to reproduce
* bugs due to race conditions. It may also be useful when designing
* concurrency control constructs such as the ones in the
* {@link java.util.concurrent.locks} package.
*/
public static native void yield();
ConcurrentHashMap
/**
* Initializes table, using the size recorded in sizeCtl.
*/
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
if ((sc = sizeCtl) < 0)
Thread.yield(); // lost initialization race; just spin
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if ((tab = table) == null || tab.length == 0) {
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}
可以看到ConcurrentHashMap用到了yield()方法,在1.8的时候可以允许多个线程对这个结构初始化的,但是在初始化的时候只允许一个线程进行初始化。因此在初始化的时候需要其他的线程让出CPU的时间片交给其他需要执行初始化的线程去执行。
yield()方法将线程从运行中的状态转化为就绪状态。
五、join()方法
join()的作用就是将指定的线程加入到当前线程,将两个交替执行的线程合并为顺序执行。
- Join方法,当前线程A等待thread线程终止之后才从thread.join()返回。线程Thread除了提供join()方法之外,还提供了join(long millis)和join(longmillis,int nanos)两个具备超时特性的方法。这两个超时方法表示,如果线程thread在给定的超时时间里没有终止,那么将会从该超时方法中返回。
1.代码实现
package com.tinner.thread;
import javax.sound.midi.Soundbank;
/**
* @Author Tinner
* @create 2019/9/18 16:31
*/
public class TestJoin {
static class Goddess implements Runnable{
private Thread thread;
public Goddess(Thread thread) {
this.thread = thread;
}
public Goddess() {
}
@Override
public void run() {
System.out.println("Goddess 开始排队打饭");
try {
if (thread != null){
thread.join();
}
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "Goddess 打饭完成");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class GoddessBoyFriend implements Runnable{
@Override
public void run() {
System.out.println("GoddessBoyFriend 开始排队打饭");
try {
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + " GoddessBoyFriend 打饭完成");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread lison = Thread.currentThread();
GoddessBoyFriend goddessBoyFriend = new GoddessBoyFriend();
Thread gbf = new Thread(goddessBoyFriend);
Goddess goddess = new Goddess(gbf);
Thread g = new Thread(goddess);
g.start();
gbf.start();
System.out.println("lison开始排队打饭......");
g.join();
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + "lison打饭完成");
}
}
以上代码是这样一个故事:lison有一个女神,女神有一个男神,有一天一起去食堂打饭,lison看到了自己女神,于是让女神排在了自己前面,而女神看到了自己的男神,于是又让自己的男神排到了自己前面,最后,男神先打完饭,女神次之,lison反倒成了最后一个打饭的,这就是join的用法
join例子
总结
join方法是将代码执行一半的时候强行插入另一个线程,只有当另一个线程执行完毕之后才执行当前线程后续的代码,让本来交替执行的线程变成顺序执行的状态。
六、线程优先级
如果要调整线程的优先级,在每一个线程new出来之后,这个thread有一个setPriority()
方法,可以设置一个1~10的数字,如果不指定,默认是5,数字越高优先级越高。设置线程的优先级可以告诉OS我这个线程的优先级比较高,给我多分配点时间片,但是对于频繁操作流的线程,默认的os分配的优先级比较低,对于那种偏于计算之类的线程默认的优先级比较高。线程的优先级在java中基本上可以忽略不计,因为设置了优先级之后,真正映射到os上之后基本没啥用,设置线程的优先级只是一个安慰操作,对于不同的平台和不同的版本内部调度时间片的方式,都不一样。
七、守护线程
当我们在代码中new出一个线程之后,称之为用户线程,用户线程和main线程的级别是一样的。守护线程主要做辅助工作,如:后台的调度、资源的回收等操作,Java中典型的:Object中的finalize()方法就是一个守护线程。java程序中只要有一个用户线程存活,整个java程序是不会退出的,但是对于守护线程而,只要java程序中没有用户线程了,哪怕有一万个守护线程,java程序也会退出。
如何把当前线程设置为守护线程?
线程类中有一个方法:setDaemon(true)
,可以将一个线程设置为守护线程
Thread g = new Thread(goddess);
g.setDaemon(true);
注意
如果run方法中有finally代码块,如果当前线程是守护线程,则finally代码块中的代码不一定会执行。因为当程序中所有的用户线程执行完毕之后,JVM会强制地将所有的守护线程关闭,不管守护线程执行到什么情况,这种情况很有可能代码执行一半,CPU时间片不会给你分配,可能就不会让你执行finally从句。一般情况下不在守护线程的finally代码块中执行业务代码。这就是为什么只在守护线程中做一些后台调度或者内存清理的工作。
网友评论