天天看点

线程常用方法-并发编程(Java)

文章目录

    • 1、yield
    • 2、线程优先级
    • 3、join
    • 4、sleep
    • 5、interrupt
    • 6、两阶段终止模式
    • 7、park
    • 8、守护线程

线程常用方法

1、yield

  1. 调用yield使当前线程冲Running状态转为Runnable状态,让具有相同优先级的其他线程获得运行机会。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
  2. 具体实现依赖于操作系统的调度器

2、线程优先级

  • Java 线程优先级使用 1 ~ 10 的整数表示:
    • 最低优先级 1:

      Thread.MIN_PRIORITY

    • 最高优先级 10:

      Thread.MAX_PRIORITY

    • 普通优先级 5(默认优先级):

      Thread.NORM_PRIORITY

  • 获取线程优先级 :线程对象.getPriority()
  • 设置线程优先级: 先处对象.setPriority()
  • 高优先级的线程比低优先级的线程有更高的几率得到执行,实际上这和操作系统及虚拟机版本相关,有可能即使设置了线程的优先级也不会产生任何作用。

参考文章:Java 多线程:线程优先级

3、join

  • 线程对象.join():无限时同步,当前线程一直等待调用join()方法的线程执行完毕,才继续执行
  • 线程对象.join(long millis):限时同步,最多等待millis毫秒后,当前线程继续执行
private static int r = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                Thread.sleep(1000);
                r = 10;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t1.start();

        log.debug("r = " + r);
           

结果:r=0,如何让程序打印10呢?想要打印10,主线程必须等待t1线程执行完毕,此时可在打印之前执行t1.join()方法

// 其他代码与上面相同...
 t1.start();
 t1.join();
 log.debug("r = " + r);
           

当给join设置参数时,结果又会如何呢?

t1.start();
t1.join(100);
log.debug("r = " + r);
           

结果:r=0

4、sleep

sleep

  1. 调用sleep方法,当前线程从Running状态转为Timed Waiting状态
  2. 其他线程可以执行interrupt方法打断在在睡眠的线程,这时会抛出InterruptedException
  3. 调用sleep方法,当前线程不会释放对象锁标记
  4. 建议使用TimeUnit的sleep代替Thread的sleep来获得更好的可读性
public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(1000);
                    log.debug("t1 线程执行...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t1.start();
        while (true) {
            TimeUnit.SECONDS.sleep(1);
            log.debug("主线程执行...");
        }
    }
           

用Thread的sleep方法设置睡眠1s和用TimeUnit的sleep设置睡眠1s,可以很直观的看出后一种的可读性更高。

TimeUnit还可以设置纳秒、微妙、秒、分钟、小时、天等等单位。

5、interrupt

  1. 可以打断正常运行的线程,此时打断标记为true,此时不会清除打断标记(值为true)
  2. 可以打断调用sleep()、wait()、join()等处于阻塞状态的线程,此时会抛出InterruptedException异常;同时会清除打断标记,既打断标志设置为false
  3. 获取打断标记方法isInterrupted(),值为true/false。
@Slf4j(topic = "cm.test01")
public class CMInterrupt {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
                log.debug("sleep...");
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
        });

        t1.start();
        Thread.sleep(100);
        log.debug("interrupt...");
        t1.interrupt();
        log.debug("t1 的打断标记:{}", t1.isInterrupted());
    }
}

2021-08-05 20:55:20 DEBUG [Thread-1] cm.test01 - sleep...
2021-08-05 20:55:20 DEBUG [main] cm.test01 - interrupt...
2021-08-05 20:55:20 DEBUG [main] cm.test01 - t1 的打断标记:false
java.lang.InterruptedException: sleep interrupted
           

在来看下打断正常运行的线程:

@Slf4j(topic = "cm.CMInterruptNormal")
public class CMInterruptNormal {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
                while (true) {

                }
        }, "t1");

        t1.start();
        Thread.sleep(100);
        log.debug("interrupt...");
        t1.interrupt();
        log.debug("t1 的打断标记:{}", t1.isInterrupted());
    }
}

2021-08-05 21:01:41 DEBUG [main] cm.CMInterruptNormal - interrupt...
2021-08-05 21:01:41 DEBUG [main] cm.CMInterruptNormal - t1 的打断标记:true

           

线程t1一直在运行,那么当线程被打断了,如果让线程t1停下来呢?此时,可以通过判断标记来结束线程执行。

// 其他代码同上
Thread t1 = new Thread(() -> {
                while (true) {
                    if (Thread.currentThread().isInterrupted()) break;
                }
        }, "t1");
           

6、两阶段终止模式

一般应用于需要随系统一直执行的某些模块,比如监控模块等。此时,监控模块设置为while(true),监控模块在不需要实施执行时,用sleep()模拟;那么监控模块可以处于2种状态:1、执行正常的监控记录等程序 2、处于睡眠状态。那么当线程被打断时,如何使在可控的情况下结束监控模块,而不影响系统其他模块的运行呢?

图示:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Lr95RLtg-1628172174411)(I:\study\java\note\concurrent\phrase2-termination-mode.png)]

代码:

@Slf4j(topic = "cm.CMPhase2terminationMode")
public class CMPhase2terminationMode {
    public static void main(String[] args) throws InterruptedException {
        Thread monitor = new Thread(() -> {
                while (true) {
                    // 判断是否打断
                    if (Thread.currentThread().isInterrupted()) {
                        log.debug("处理善后");
                        break;
                    }

                    try {
                        // 睡眠
                        TimeUnit.SECONDS.sleep(1);
                        // 监控记录
                        log.debug("监控记录执行...");
                    } catch (InterruptedException e) {
                        // 设置打断标记
                        Thread.currentThread().interrupt();
                        e.printStackTrace();
                    }
                }
        }, "t1");

        log.debug("监控模块开始执行...");
        monitor.start();
        TimeUnit.MILLISECONDS.sleep(3500);
        // 模拟意外打断情况
        monitor.interrupt();
        log.debug("监控模块关闭...");
    }
}
           

如果不在catch模块中添加重新设置打断标记,那么程序就不会结束。

7、park

  1. LockSupport的方法,会让线程无限时暂停执行
  2. interrupt()方法可打断暂停状态,让线程继续执行
  3. 打断标记为true时,park方法不会生效
  4. interrupted()判断打断标记,同时会清除打断标记

代码测试:

@Slf4j(topic = "cm.CMPark")
public class CMPark {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            log.debug("park...");
            LockSupport.park();
            log.debug("unpark...");
            log.debug("线程状态:{}", Thread.currentThread().isInterrupted());
            LockSupport.park();
            log.debug("unpark...");
        });

        t1.start();
        TimeUnit.SECONDS.sleep(1);
        t1.interrupt();
    }
}

2021-08-05 21:50:13.084 DEBUG [Thread-1] cm.CMPark - park...
2021-08-05 21:50:14.083 DEBUG [Thread-1] cm.CMPark - unpark...
2021-08-05 21:50:14.083 DEBUG [Thread-1] cm.CMPark - 线程状态:true
2021-08-05 21:50:14.084 DEBUG [Thread-1] cm.CMPark - unpark...
           

1秒之后执行打断,当再次执行park的时候,没有生效,此时,可以通过interrupted()方法判断并清除标记

log.debug("unpark...");
log.debug("线程状态:{}", Thread.interrupted());
LockSupport.park();
           

8、守护线程

Java进程,只有当其所有线程执行结束的时候,才会结束。守护线程,顾名思义,不管守护线程有没有执行完成,当前守护的线程结束的时候,守护线程也会结束执行。

代码示例:

@Slf4j(topic = "cm.CMDaemon")
public class CMDaemon {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while (true) {
                if (Thread.currentThread().isInterrupted()) break;
            }
            log.debug("守护线程执行结束...");
        }, "t1");
        t1.setDaemon(true);
        t1.start();
        TimeUnit.SECONDS.sleep(1);
        log.debug("主线程执行结束...");
    }
}
           

tips:

  1. 如果一个线程要设置为守护线程,通过调用setDaemon(true)方法,一定要在线程启动之前设置,否则无效

继续阅读