并发编程[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线程工作中...

输出就两行,程序自动退出。(具体输出几行是看当时的执行情况,总之程序会在主线程结束之后结束。)

posted @ 2024-08-23 10:39  Aeons  阅读(1)  评论(0编辑  收藏  举报