第三天主要学习线程中的常见方法~
1.常见方法概述
方法名 | 功能说明 | 注意 |
---|---|---|
start() | 启动一个新线程,在新的线程运行run方法中的代码 | start方法只是让线程进入就绪,里面代码不一定立刻运行(CPU的时间片还没分给它)。每个线程对象的start方法只能调用一次,如果调用了多次会出现IllegalThreadStateException |
run() | 新线程启动后就会调用run方法 | 如果在构造Thread的时候传入了Runnable参数,则线程启动后会调用Runnble中的run方法,否则默认不执行任何操作,但可以创建Thread的子类对象,来覆盖默认行为(就是重写run方法) |
join() | 等待线程运行结束 | 用于线程间的通信 |
join(long n) | 等待线程运行结束,但是最多等待n毫秒 | |
getId() | 获取线程长整型的id | id唯一 |
getName() | 获取线程名 | |
setName(String) | 设置线程名 | |
getPriority() | 获取线程优先级 | |
setPriority(String) | 修改线程优先级 | java中规定线程优先级是1~10的整数,较大优先级能提高给线程被CPU调度的几率,但是在java中这么设置效果不是很明显 |
getState() | 获取线程状态 | Java中线程状态是用6个enum表示,分别为:NEW、RUNNABLE、BLOCKED、WAITING、TIMED、TERMINATED |
isInterrupted() | 判断是否被打断 | 不会清除打断标记 |
isAlive() | 判断线程是否还存活(即线程还有没有运行完毕) | 不会清除打断标记 |
interrupt() | 打断线程 | 如果被打断线程正在sleep,wait,join会导致被打断的线抛出nterruptedException,并清除打断标记;如果打断的是正在运行的线程,则会设置打断标记;park的线程被打断,也会设置打断标记 |
interrupted() | (static)判断当前线程是否被打断 | 会清除打断标记 |
currentThread() | (static)获取当前正在执行的线程 | |
sleep(long n) | (static)让当前执行的线程休眠n毫秒,休眠期间让出cpu时间片给其他线程 | |
yield() | (static)提示线程调度器让出当前线程对CPU的使用 | 主要是为了测试和调试 |
2.start & run
start方法最后还是会调用run方法,那我们直接调用run方法不可以吗?
不可以,因为start方法在调用run方法之前,是先开启了一个新线程,然后新开启的线程再调用run方法执行任务,而直接调用run方法就是纯粹的方法调用,是由主线程进行的。
案例:
@Slf4j(topic="c.Test7")
public class Test7 {
public static void main(String[] args) {
Thread t1 = new Thread("t1"){
@Override
public void run() {
log.debug("running");
}
};
// run只是对方法的调用,还是在主线程中调用的run,而start则是开启线程并由开启的新线程调用run方法
t1.run();
t1.start();
}
}
执行结果:
17:25:10.780 c.Test7 [main] - running
17:25:10.787 c.Test7 [t1] - running
可见直接调用run方法是由主线程调用的run方法
3.getState
直接看案例
@Slf4j(topic="c.Test8")
public class Test8 {
public static void main(String[] args) {
//演示线程状态
Thread t1 = new Thread("t1"){
@Override
public void run() {
log.debug("running");
}
};
System.out.println(t1.getState());
t1.start();
System.out.println(t1.getState());
t1.start();
}
}
执行结果:
NEW
RUNNABLE
17:26:50.561 c.Test8 [t1] - running
Exception in thread "main" java.lang.IllegalThreadStateException
at java.lang.Thread.start(Thread.java:710)
at com.example.juc.test.Test8.main(Test8.java:23)
线程状态为runnable的时候再进行start,则会抛出IllegalThreadStateException异常
4.sleep & yield
sleep和yield都可以使线程进行休眠。
(1)sleep
- sleep可以使线程从Running状态进入Timed Wating状态(TIMED_WAITING)
案例:
@Slf4j(topic="c.Test9")
public class Test9 {
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("running");
}
};
t1.start();
log.debug("t1.state:{}",t1.getState());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("t1.state:{}",t1.getState());
}
}
主线程在启动t1线程后就进入休眠,以确保主线程苏醒后t1是休眠状态来获取t1的状态。
执行结果:
17:49:27.253 c.Test9 [main] - t1.state:RUNNABLE
17:39:13.491 c.Test9 [main] - t1.state:TIMED_WAITING
17:39:13.988 c.Test9 [t1] - running
- 其他线程可以使用interrupt方法打断正在睡眠的线程,这时sleep方法会抛出InterruptedException
案例:
@Slf4j(topic="c.Test10")
public class Test10 {
public static void main(String[] args) {
Thread t1 = new Thread("t1"){
@Override
public void run() {
try {
log.debug("t1线程进入睡眠...");
Thread.sleep(2000);
} catch (InterruptedException e) {
log.debug("wake up!");
e.printStackTrace();
}
}
};
t1.start();
try {
log.debug("主线程进入睡眠...");
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("主线程即将中断t1的睡眠...");
t1.interrupt();
}
}
执行结果:
17:59:57.693 c.Test10 [main] - 主线程进入睡眠...
17:59:57.698 c.Test10 [t1] - t1线程进入睡眠...
17:59:58.204 c.Test10 [main] - 主线程即将中断t1的睡眠...
17:59:58.205 c.Test10 [t1] - wake up!
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.example.juc.test.Test10$1.run(Test10.java:17)
- 睡眠结束后的线程未必会立刻得到执行,需要等到任务调度器将时间片分给此线程时才能继续执行
- 建议用TimeUnit的sleep代替Thread的sleep来获得更好的可读性,
Thread t1 = new Thread("t1"){
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1); //睡眠一秒
TimeUnit.MINUTES.sleep(1); //睡眠一分钟
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
- sleep的应用
在编程中如果需要服务器的话,我们要确保服务器一直在运行,所以会让服务器一直执行一个while(true)循环,而为了防止服务器只执行一个while(true)空转,一般都会写为:
来防止服务器空转浪费cpu。while (true){ Thread.sleep(50); }
- 可以用wait或条件变量达到类以的效果
- 不同的是,后两种都需要加锁,并且需要相应的唤醒操作,一般适用于要进行同步的场景
- sleep适用于无需锁同步的场景
(2)yield
-
调用yield会让当前线程从Running进入Runnable就绪状态,然后调度执行其他线程,yield的本意即“让出”
-
具体的实现还是要依赖操作系统的任务调度器,可能“让也让不出去”
-
和sleep的区别:
- sleep是让线程从Running进入Time Wating状态,yield是让线程从Running进入Runnable状态,一个线程是可能再从Runnable状态变成Running状态的,而Time Watiing阻塞状态不能直接转变成Running状态:
- sleep会有一个等待时间,而yield没有等待时间,直接让出
- sleep是让线程从Running进入Time Wating状态,yield是让线程从Running进入Runnable状态,一个线程是可能再从Runnable状态变成Running状态的,而Time Watiing阻塞状态不能直接转变成Running状态:
-
线程优先级:
1.setPriority方法:设置优先级,1-10,越大优先级越高,默认为5
2.线程优先级会提示(hint)调度器优先调度该线程但它仅仅是一个提示,调度器可以忽略它
3.如果cpu比较忙,那么优先级高的线程会获得更多的时间片,但cpu闲时,优先级几乎没有作用,所以yield跟线程优先级不是很容易能控制任务的调度
5.join方法
join方法的作用就是等到某个线程执行结束,让线程进入TIMED_WAITING状态来等待某个线程执行结束。
join方法的应用之一就是实现同步;
首先回顾同步的概念:
- 需要等待结果返回才能继续运行就是同步
- 不需要等待结果返回就能继续运行就是异步
案例:
@Slf4j(topic="c.Test12")
public class Test12 {
public static void main(String[] args) throws InterruptedException {
test();
}
public static void test() throws InterruptedException {
Thread t1 = new Thread("t1") {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("t1 running");
}
};
Thread t2 = new Thread("t2") {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("t2 running");
}
};
t1.start();
t2.start();
log.debug("开始执行...");
long startTime = System.currentTimeMillis();
log.debug("t1_join...");
t1.join();
log.debug("t2_join...");
t2.join();
long endTime = System.currentTimeMillis();
log.debug("执行时间为:{}",endTime - startTime);
}
}
执行结果:
20:07:45.393 c.Test12 [main] - 开始执行...
20:07:45.395 c.Test12 [main] - t1_join...
20:07:46.395 c.Test12 [t1] - t1 running
20:07:47.401 c.Test12 [t2] - t2 running
20:07:47.401 c.Test12 [main] - t2_join...
20:07:47.401 c.Test12 [main] - 执行时间为:2006
在主线程中执行t1.join(),只会使主线程等待t1执行结束,t2不受影响继续执行,所以最后整个执行时间就是2秒
图例:
- join的限时同步:join(long mills),当线程执行时长超过mills时,就不会继续等待线程执行结束,直接执行之后的代码。
6.interrupt方法
interrupt方法可以“”打断“sleep、wait、join这些让线程处于阻塞的线程,也可以打断运行状态的线程,线程被打断后会抛出 InterruptedException,注意这里的打断并不是让这些线程强制改变运行状态,而是只改变打断标记:
打断标记:打断标记是一个boolean值。当线程被打断后,打断标记会变为true,但是打断sleep、wait、join的线程,会清空打断标记,也就是重置为false。
isInterrupted方法:查看打断方法。
案例:
@Slf4j(topic="c.Test14")
public class Test14 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread("t1"){
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t1.start();
Thread.sleep(1000);
log.debug("interrupt...");
t1.interrupt();
Thread.sleep(1000);
log.debug(String.valueOf(t1.isInterrupted()));
}
}
执行结果:
20:26:10.150 c.Test14 [main] - interrupt...
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.example.juc.test.Test14$1.run(Test14.java:16)
20:26:10.152 c.Test14 [main] - true
提问:为什么在t1.interrupt()后还要让主线程再睡一会儿?
因为如果此时马上打印输出t1的打断标记,此时很可能还是输出的true而不是false,那是因为这个时间段内打断标记还没来得及被清除,所以先让主线程睡一会儿再打印就能让打断标记来得及被清除了。
那么interrupt有什么作用呢?
首先我们需要知道,interrupt只是改变打断标记,要想通过interrupt实现对线程运行状态的控制,则需要我们自定义逻辑规则。
案例:
@Slf4j(topic="c.Test15")
public class Test15 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread("t1"){
@Override
public void run() {
int i = 0;
while (true){
try {
Thread.sleep(5000);
i++;
log.debug("{}",i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t1.start();
Thread.sleep(1000);
t1.interrupt();
}
}
执行结果:
20:43:19.740 c.Test15 [t1] - 1
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.example.juc.test.Test15$1.run(Test15.java:18)
20:43:20.747 c.Test15 [t1] - 2
20:43:21.259 c.Test15 [t1] - 3
20:43:21.772 c.Test15 [t1] - 4
20:43:21.259 c.Test15 [t1] - 5
20:43:21.772 c.Test15 [t1] - 6
.........
.........
.........
.........
.........
.........
上述案例只是对线程状态进行了改变,没有对线程状态进行判断,所以会一直执行下去。
修改后的案例:
@Slf4j(topic="c.Test15")
public class Test15 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread("t1"){
@Override
public void run() {
int i = 0;
while (true){
if (Thread.currentThread().isInterrupted()){
log.debug("跳出循环");
break;
}
i++;
log.debug("{}",i);
}
}
};
t1.start();
Thread.sleep(1000);
log.debug("interrupt...");
t1.interrupt();
log.debug(String.valueOf(t1.isInterrupted()));
}
}
执行结果:
....
....
....
20:50:22.506 c.Test15 [t1] - 273376
20:50:22.506 c.Test15 [t1] - 273377
20:50:22.506 c.Test15 [t1] - 273378
20:50:22.506 c.Test15 [main] - interrupt...
20:50:22.506 c.Test15 [t1] - 273379
20:50:22.507 c.Test15 [main] - true
20:50:22.507 c.Test15 [t1] - 跳出循环
需要将t1线程内的sleep去掉,否则打断sleep线程不会改变打断标记。
上面的案例可以通过控制对打断标记的判断来控制线程的运行。