多线程
一、线程状态
Thread.State是一个枚举类型,里边包含了线程的所有状态。
我们通过代码来看一下线程都有哪些状态:
public class ThreadState {
public static void main(String[] args) {
for (Thread.State state : Thread.State.values()) {
System.out.println(state);
}
}
}
运行结果:
线程状态主要分为:创建、运行、阻塞、等待、超时等待、终止这几种状态。
下图是线程状态的转换图,对于不同方法的调用线程状态会转变。下面将具体了解这些方法。
二、Thread相关API
1.Thread所有方法
1>静态方法
调用静态方法的代码行处于哪个线程中,就是获取哪个线程的属性。
- static int activeCount ():返回当前线程的thread group及其子组中活动线程数的估计(了解);
- static Thread currentThread() :返回对当前正在执行的线程对象的引用。这个方法处于哪个线程就获取这个线程的引用;
- static boolean interrupted():判断当前线程是否中断,
如果为true(当前线程为中断状态),就清除中断标志
; - static void sleep(long mi1lis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。时间单位是毫秒;
- static void yield() :线程让步,作用------->将当前线程由运行态转变为就绪态;
Thread.xxx:都是静态方法,作用于当前线程。但wait()方法是实例方法,也作用于当前线程
2>实例方法
- long getId():返回此线程的标识符。(了解)
- String getName ():返回此线程的名称。
- int getPriority():优先级更高的线程,更有可能先执行,但不是一定,只返回此线程的优先级。是几率更大 (了解)
- Thread.State getState():返回此线程的状态。
- void interrupt():中断这个线程。 仅仅是将线程的中断标志位设为true。
- boolean isAlive():测试这个线程是否活着。(了解)
- boolean isDaemon():测试这个线程是否是守护线程。(了解)
- boolean isInterrupted():测试这个线程是否被中断,
不会清除中断标志
。 - void join():等待这个线程死亡。
- void join(long millis):等待这个线程死亡最多mill1s毫秒。
- void run ( ):在线程内如果我们自己定义了run方法,启动线程就会执行我们自己定义的run方法,否则就执行线程自身的。
- void setDaemon (boolean on):将此线程标记为daemon线程或用户线程。(了解)
- void setName (String name ):将此线程的名称更改为等于参数name 。(了解)
- void setPriority (int newPriority):更改此线程的优先级。(了解),设置的优先级越高,线程就有可能先执行,不是一定先执行
- void start():使该线程开始执行; Java虚拟机调用此线程的run方法。
2.线程启动:start()
注意:
我们要知道start()实例方法是启动该线程,此时线程由创建态变为就绪态,当线程为就绪态时就可以等待操作系统进行调度,调度到之后就执行线程的run方法(线程的任务)。
下面我们先来看一段代码:
public class ThreadFirst {
public static void main(String[] args)throws InterruptedException {
//新创建一个线程
Thread newthread= new Thread(new Runnable() {
@Override
public void run()
{
try {
Thread.sleep(99999999999L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
newthread.start();
}
}
我们运行该代码,并且在cmd中输入jconsole连接到Java监视窗口。如下图所示:
重点:
- Thread newthread= new Thread(new Runnable()…);
仅仅是创建了一个线程对象
,此时相当于创建了上图的Thread-0线程,并且该线程的状态为创建态
。 - newthread.start(); 执行这行代码时,
Thread-0线程由创建态转变为就绪态
,此时就可以等待操作系统调度,调度到了就转变为运行态(执行线程中的run方法)。 - 线程之间是可以并发也可以并行的(具体看操作系统)。见下图:
3.线程让步: Thread.yield()
线程让步就是让该线程由运行态转变为就绪态。一般用于让其他线程完成自己的任务后,再执行当前线程的工作。
先来看下面代码:
public class ThreadYield {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}).start();
//等待new Thread所有线程执行完毕,才会执行别的操作(main线程打印main线程的名字)
while (Thread.activeCount()>1)//当前活跃的线程数大于1时就执行循环里边的代码(让main线程由运行态转变为就绪态),直到new Thread线程执行完毕,所以先打印thread-0,后打印main
{
//将当前线程由运行态转变为就绪态
Thread.yield();
}
System.out.println(Thread.currentThread().getName());
}
}
Thread.activeCount()>1:当前活跃的线程数大于1时就执行循环里边的代码(让main线程由运行态转变为就绪态)
那么这段代码运行结果会先打印main还是Thread-0线程呢?精华就在while循环这块了。
先来看一下运行结果,再进行一步一步分析:
从结果可以看出,是先打印的Thread-0线程。分析:
- 当Thread-0线程通过start方法启动起来之后,当前线程是2个(一个是Thread-0线程,另一个就是Java的main线程)
- 当代码走到while循环时,先判断当前线程的数量是否大于1,显然2>1,然后就执行while循环体( Thread.yield():当前线程让步)
- 对于Thread.yield()这行代码来说当前线程为Java main线程,所以此时main线程让步,一直等待Thread-0线程执行完run方法(打印Thread-0),直到Thread-0线程退出,活跃线程数就为1,再打印main线程名。
简单来说,线程让步,就是将正在运行的线程停止手中的工作,让其他线程先行,其他线程做完自己的活之后再做自己的工作。
4.守护线程:.setDaemon(true)
- 用户线程: 我们平常创建的普通线程。
- 守护线程: 用来服务于用户线程;不需要上层逻辑介入。
我们先通过一段代码观察一下效果:
public class DaemonThread {
public static void main(String[] args) {
Thread t=new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(999999999L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//设置守护线程
t.setDaemon(true);
t.start();
}
}
设置守护线程:
没有设置守护线程:
通过结果发现:没有设置守护线程的一直处于没有退出。
因此:
当线程只剩下守护线程的时候,JVM就会退出;如果还有其他的任意一个用户线程还在,JVM就不会退出。
注意:
thread.setDaemon(true):必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
5.线程的等待:join()/activeCount()+yield()
1)join()
join():让当前线程进行阻塞(运行态–》阻塞态)等待(需要一定的条件);比如线程B(当前线程)中调用了线程A的Join()方法,直到线程A执行完毕或者等待线程A执行指定的时间后,才会继续执行线程B。
- 有参方法( t.join(1000) ):等待 t 线程1000毫秒 ,1000毫秒后当前线程代码向下执行。
- 无参方法( t.join() ) :等待线程 t 执行完毕,当前线程再向下执行代码。
代码:
public class ThreadJoin {
public static void main(String[] args) {
Thread t=new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
//当前线程: t.join();调用这行代码的时候时是在哪个线程,这个线程就是当前线程
//t是new Thread这个线程的引用
//让t这个线程先执行完,再执行main线程
try {
t.join(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
上述代码就是让main线程阻塞2000毫秒(让t线程先执行2000毫秒)之后再执行main线程的代码。
2)activeCount()结合yield()
上面说线程让步已经使用过一次。
- Thread.activeCount()>1:当前活跃的线程数大于1时就执行循环里边的代码
- Thread.yield(): 将当前线程由运行态转变为就绪态(让main线程由运行态转变为就绪态)
相当于等待Thread-0线程执行完,再执行main线程。
6.中断这个线程:interrupt()
interrupt():表示中断该线程,它并没有让正在运行线程停止了,只是将该线程的中断标志位设置为true。
中断标志位:
-
线程启动以后:中断标志位=false
-
在线程运行态中,处理线程中断,需要自行通过判断中断标志位,来进行中断的处理逻辑。判断标志位方法:
- Thread.interrupted(): 返回当前线程的中断标志位,并
清除中断标志(恢复为false)
- Thread.currentThread().isInterrupted(): 返回指定线程的中断标志位,
不清除中断标志
- Thread.interrupted(): 返回当前线程的中断标志位,并
-
通过 thread 对象调用 interrupt() 方法通知该线程停止运行,thread 收到通知的方式有两种:
- 如果线程调用了 wait/join/sleep 等方法而阻塞挂起,则以 InterruptedException 异常的形式通知,并重置中断标志=true。
- 否则,其他情况只是内部的一个中断标志被设置为true,thread 可以继续运行。
public static void test4()
{
Thread t=new Thread(new Runnable() {
@Override
//如果线程调用了 wait/join/sleep 等方法而阻塞挂起,则以 InterruptedException 异常的形式通知,清除
//中断标志
public void run() {
for (int i = 0; i < 10; i++)
{
//调用静态方法:返回中断标志位,返回后还要重置中断标志位(只有标志位是true,才会重置为false)
System.out.println(Thread.interrupted());//一个true,九个false
//不会重置中断标志位
System.out.println(Thread.currentThread().isInterrupted());//十个false
}//一个true,九个false
}
});
//启动子线程
t.start();//t线程的中断标志位=false
//在main线程中中断子线程
t.interrupt();//t线程的中断标志位=true
}
public static void main(String[] args) {
//test1();
//test2();
test4();//抛出异常
}
运行结果: