并发编程[2]_线程的常用方法
1. run() 和 start()
start() 方法让线程进入就绪状态
run() 方法 是Runnable 中的一个抽象方法,线程启动时就会调用run() 方法
(1) 如果直接调用run()方法,是不会启动新线程的
public class Test1 {
private static final Logger log = LoggerFactory.getLogger(Test1.class);
public static void main(String[] args) {
Thread thread = new Thread("thread1") {
@Override
public void run() {
log.debug("running...");
}
};
log.debug("main...");
thread.run();
}
}
运行结果:
2021-04-14 21:02:55 [main] - main...
2021-04-14 21:02:55 [main] - running...
可以看出log输出内容都是在 [main]线程中输出的, 并不是在thread1线程中执行的。
(2) start() 方法调用多次会报IllegalThreadStateException异常
public class Test1 {
private static final Logger log = LoggerFactory.getLogger(Test1.class);
public static void main(String[] args) {
Thread thread = new Thread("thread1") {
@Override
public void run() {
log.debug("running...");
}
};
log.debug("main...");
thread.start();
thread.start();
}
}
运行结果:
2021-04-14 21:24:34 [main] - main...
Exception in thread "main" 2021-04-14 21:24:34 [thread1] - running...
java.lang.IllegalThreadStateException
at java.lang.Thread.start(Thread.java:708)
at com.yt.concurrency.p1.Test1.main(Test1.java:24)
2. getState()
getState()方法是获取线程当前状态,返回值是State类型(Thread类中的enum类型), 6个值分别是 NEW,RUNNABLE, BLOCKED, WAITING,TIMED_WAITING, TERMINATED
public static void main(String[] args) {
Thread thread = new Thread("thread1") {
@Override
public void run() {
log.debug("running...");
}
};
log.debug("before:{}", thread.getState().toString());
thread.start();
log.debug("after:{}", thread.getState().toString());
}
运行结果:
2021-04-14 21:31:34 [main] - before:NEW
2021-04-14 21:31:34 [main] - after:RUNNABLE
2021-04-14 21:31:34 [thread1] - running...
3. sleep()
sleep() 能让线程暂时休眠指定毫秒数,并让线程从Running(RUNNABLE)状态进入Timed Waiting(TIMED_WAITING)状态
调用sleep() 方法进入等待状态时,能够被其他线程使用interrupt()方法打断,然后抛出InterruptedException异常
public class Test1 {
private static final Logger log = LoggerFactory.getLogger(Test1.class);
public static void main(String[] args) {
Thread thread = new Thread("thread1") {
@Override
public void run() {
try {
// 休眠4秒
Thread.sleep(4000);
} catch (InterruptedException e) {
log.debug("4.线程被打断");
e.printStackTrace();
}
}
};
log.debug("1.线程未启动:{}", thread.getState().toString());
thread.start();
log.debug("2.线程刚启动:{}", thread.getState().toString());
try {
// 主线程休眠2秒,便于观察
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug("3.线程休眠中:{}", thread.getState().toString());
thread.interrupt();
}
}
运行结果:
2021-04-14 22:23:40.608 [main] - 1.线程未启动:NEW
2021-04-14 22:23:40.608 [main] - 2.线程刚启动:RUNNABLE
2021-04-14 22:23:42.608 [main] - 3.线程休眠中:TIMED_WAITING
2021-04-14 22:23:42.608 [thread1] - 4.线程被打断
4. yield()
yield() 方法会让线程从running状态进入runnable状态,把自己cpu执行的时间让掉,让其他线程或者还是自己的这个线程来运行,具体还是得由操作系统的调度器做决定
5. join()
join() 方法会让调用线程同步等待被调用的线程,可以加参数等待毫秒数 join(long millis) 。
测试:新建一个线程,sleep一秒后为静态变量a赋值,然后主线程正常读取变量a。
public class Test1 {
private static final Logger log = LoggerFactory.getLogger(Test1.class);
private static int a = 0;
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread("thread1") {
@Override
public void run() {
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
a = 10;
}
};
thread1.start();
// 暂时注释掉join方法
// thread1.join();
log.debug("a:{}", a);
}
}
此时的输出结果为:
2021-04-14 23:27:59.210 [main] - a:0
因为main方法输出a的时候,线程thread1还未给a赋值,所以读取到的是0。
这时将代码中加上 thread1.join();
此时的输出结果为:
2021-04-14 23:31:04.955 [main] - a:10
main方法会同步等待thread1线程结束,也可使用 thread1.join(2000) 来为join方法指定等待时间为2秒。
6. interrupt()
interrupt() 方法可以打断线程,有一个打断标志位可以标志是否被打断,使用isInterrupted() 查看。值得一提的是,打断阻塞的线程时,例如sleep()被打断,会报出InterruptedException()异常,且标志位会自动清除,重新置为false,而打断正常运行的线程时,会把标志位置为true,但是线程仍会正常执行。
与isInterrupted() 方法相同作用的 interrupted()方法也是查看打断标志位,区别是,调用isInterrupted()方法后,不会清除标志位,而调用interrupted()方法则会。
打断sleep()线程:
public class Test1 {
private static final Logger log = LoggerFactory.getLogger(Test1.class);
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread("thread1") {
@Override
public void run() {
try {
log.debug("打断标志1: {}", Thread.currentThread().isInterrupted());
log.debug("sleep");
sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
log.debug("打断标志2: {}", Thread.currentThread().isInterrupted());
}
}
};
thread1.start();
// 主线程睡眠两秒,保证打断时thread1是处在sleep状态
Thread.sleep(2000);
thread1.interrupt();
}
}
运行结果:
2021-04-15 20:51:55.322 [thread1] - 打断标志1: false
2021-04-15 20:51:55.322 [thread1] - sleep
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.yt.concurrency.p1.Test1$1.run(Test1.java:21)
2021-04-15 20:51:57.321 [thread1] - 打断标志2: false
打断正常线程:
public class Test1 {
private static final Logger log = LoggerFactory.getLogger(Test1.class);
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread("thread1") {
@Override
public void run() {
log.debug("打断标志1:{}",Thread.currentThread().isInterrupted());
while(true){
if(Thread.currentThread().isInterrupted()){
log.debug("打断标志2:{}",Thread.currentThread().isInterrupted());
log.debug("被打断,退出循环...");
break;
}
}
}
};
thread1.start();
Thread.sleep(1000);
thread1.interrupt();
}
}
运行结果:
2021-04-15 20:54:08.953 [thread1] - 打断标志1:false
2021-04-15 20:54:09.952 [thread1] - 打断标志2:true
2021-04-15 20:54:09.952 [thread1] - 被打断,退出循环...
例子中,因为打断正常线程时,线程不会终止或报异常,所以得判断打断标志位是否为true,来退出循环
- 两阶段终止模式
两阶段终止模式可以优雅地终止线程,如果用stop()方法终止线程(不推荐使用),会直接将线程结束,如果线程持有锁,便来不及释放,有可能会导致程序发生错误,所以用interrupt()方法来设置打断标志位(阻塞状态时会抛 InterruptedException 异常),是比较好的方式。
举个例子:
public class Test1 {
private static final Logger log = LoggerFactory.getLogger(Test1.class);
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread("thread1") {
@Override
public void run() {
while(true){
// 1. 如果此时被打断,则会进行收尾操作,然后退出循环
if(Thread.currentThread().isInterrupted()){
log.debug("线程终止前的一些收尾操作");
break;
}
log.debug("线程运行中...");
try {
Thread.sleep(3000);
log.debug("业务逻辑");
} catch (InterruptedException e) {
// 2. 如果是在阻塞状态中被打断,则需要重新设置状态位,下次循环判断的时候才会收尾
e.printStackTrace();
log.debug("睡眠时被打断,会把打断标志位设置为false,所以需要重新设置打断标志位。");
Thread.currentThread().interrupt();
}
}
}
};
thread1.start();
Thread.sleep(1000);
log.error("准备打断thread1线程");
thread1.interrupt();
}
}
运行结果:
2021-04-15 22:23:51.828 [thread1] - 线程运行中...
2021-04-15 22:23:52.828 [main] - 准备打断thread1线程
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.yt.concurrency.p1.Test1$1.run(Test1.java:26)
2021-04-15 22:23:52.829 [thread1] - 睡眠时被打断,会把打断标志位设置为false,所以需要重新设置打断标志位。
2021-04-15 22:23:52.829 [thread1] - 线程终止前的一些收尾操作
7. setDaemon()
可以使用setDaemon(true)设置线程为守护线程。非守护线程都结束之后,守护线程会随着JVM一同结束工作。 垃圾回收器就是一个常见的守护线程。
普通线程,若一个线程未结束,则程序不会结束
public class Test1 {
private static final Logger log = LoggerFactory.getLogger(Test1.class);
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread("thread1") {
@Override
public void run() {
while(true){
log.debug("thread1线程工作中...");
}
}
};
thread1.start();
log.debug("主线程结束...");
}
}
输出为
2021-04-15 22:39:42.220 [main] - 主线程结束...
2021-04-15 22:39:42.220 [thread1] - thread1线程工作中...
2021-04-15 22:39:42.221 [thread1] - thread1线程工作中...
......
......
主线程结束后,thread1线程并未结束,程序也没有退出,正无限执行thread1中的代码。
将thread1线程设置为守护线程,加上thread1.setDaemon(true)
public class Test1 {
private static final Logger log = LoggerFactory.getLogger(Test1.class);
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread("thread1") {
@Override
public void run() {
while (true) {
log.debug("thread1线程工作中...");
}
}
};
// 在start前设置,不然未生效
thread1.setDaemon(true);
thread1.start();
log.debug("主线程结束...");
}
}
输出为
2021-04-15 22:43:55.032 [main] - 主线程结束...
2021-04-15 22:43:55.032 [thread1] - thread1线程工作中...
输出就两行,程序自动退出。(具体输出几行是看当时的执行情况,总之程序会在主线程结束之后结束。)