Java中线程的常用方法
sleep
- 调用sleep会让当前线程从
Running
进入TIMED WAITING
状态 - 其它线程可以使用
interrupt
方法打断正在睡眠的线程,这时sleep方法会抛出InterruptedException
- 睡眠结束后的线程未必会立刻得到执行
- 建议用TimeUnit的sleep代替
Thread
的sleep来获得更好的可读性
实例代码
public class SleepMethodDemo {
public static void main(String[] args) {
Logger log = Logger.getLogger("SleepMethodDemo");
Thread t1 = new Thread(() -> {
try {
// Thread.sleep(1000);
//建议使用
log.info("Thread t1 sleep 1000ms");
TimeUnit.MICROSECONDS.sleep(1000);
} catch (InterruptedException e) {
log.info("Thread t1 interrupted");
e.printStackTrace();
}
}, "t1");
t1.start();
log.log(Level.INFO, "main thread end");
t1.interrupt();
}
}
控制台输出
这里的TimeUtit本质上还是去调用了Thread.sleep(),只是可读性增强,我们可以查看一下源码
yield
- 调用yield会让当前线程从
Running
进入Runnable
就绪状态,然后调度执行其它线程 - 具体的实现依赖于操作系统的任务调度器(当cpu很空闲时,也就是没有太多资源抢占到cpu,哪怕执行此方法,操作系统也会将时间片给当前线程)
sleep 和 yield的区别
- sleep会有一段真正的等待时间,而yield基本上是马上执行
- sleep是将线程变为
TIMED_WAITING
状态,此时是不会被操作系统调度的,而yield是将线程变为就绪状态,是可以继续被系统调度的,也就是还是能够抢占cpu.
线程优先级
- 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它
- 如果cpu比较忙,那么优先级高的线程会获得更多的时间片,但cpu闲时,优先级几乎没作用.
public class YieldAndPriority {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
int i = 0;
while (true) {
System.out.println(Thread.currentThread().getName() + " " + i++);
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
int i = 0;
while (true) {
Thread.yield();//让出cpu
System.out.println(" "+Thread.currentThread().getName() + " " + i++);
}
}
});
//设置优先级,优先级范围为1-10
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.start();
}
}
sleep的应用
在没有利用cpu来计算时,不要让while(true)空转浪费cpu,这时可以使用yield或sleep来让出cpu的使用权给其他程序
while(true){
try{
Thread.sleep(50);
}catch(InterruptedException e){
e.printStackTrace();
}
}
- 可以用wait或条件变量达到类似的效果
- 不同的是,后两种都需要加锁,并且需要相应的唤醒操作,一般适用于要进行同步的场景
- sleep适用于无需锁同步的场景.
join
join方法实际上就是在一个线程当中等待另一个线程结束以后才开始执行自己的代码
该方法有两种,一种带参一种无参.
- join() 等待线程运行结束
- join() 等待线程运行结束,最多等待n毫秒
若线程执行完毕小于设定的最多等待时间,不会继续等待,以两者比较的最小值为准.
interrupt
打断 sleep,wait,join
的线程,会清空打断状态,打断过后,打断状态设为false.正常线程打断设置为true
(但正常线程打断并不会立即停止运行,只是注上一个打断标记)
- 示例: 打断
sleep,wait,join
线程.
public class InterruptMethodDemo {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
System.out.println("t1 is interrupted");
e.printStackTrace();
}
}, "t1");
System.out.println("start t1");
t1.start();
t1.interrupt();
TimeUnit.MICROSECONDS.sleep(50);
System.out.println("t1.isInterrupted() = " + t1.isInterrupted());
}
}
结果展示
- 示例:打断正常的线程
public class InterruptNormalDemo {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (true) {
}
}, "t1");
System.out.println("t1.start");
t1.start();
t1.interrupt();
System.out.println("t1.interrupt()");
TimeUnit.SECONDS.sleep(1);
System.out.println("t1.isInterrupted() = " + t1.isInterrupted());
}
}
此时,线程t1并没有结束,而是继续在运行.
我们将代码改写,手动控制线程的执行与停止
public class InterruptNormalDemo {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (true) {
System.out.println("while t1.isInterrupted() = " + Thread.currentThread().isInterrupted());
if(Thread.currentThread().isInterrupted()){
break;
}
}
}, "t1");
System.out.println("t1.start");
t1.start();
Thread.sleep(1);
t1.interrupt();
System.out.println("t1.interrupt()");
TimeUnit.SECONDS.sleep(1);
System.out.println("t1.isInterrupted() = " + t1.isInterrupted());
}
}
结果为
实践运用interrupt(两阶段终止)
场景分析: 当前有一个业务,用来日志监控,每隔两秒进行监控,中途若被打断,停止监控.
我们可以运用两阶段终止的设计模式来进行.哪么是哪两个阶段呢
- 第一个阶段,正常打断,也就是我们没有在线程睡眠时打断,此时打断标记为
true
- 第二个阶段,睡眠打断,也就是我们在线程睡眠时打断,此时打断标记为
false
为了完成此业务,我们应该在睡眠打断后抛出异常的过程中,再次重置打断标记,也就是置为false,这样我们就能正确停止.
代码如下
@Slf4j(topic = "c.DoubleInterruptDemo")
public class DoubleInterruptDemo {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (true){
Thread currentThread = Thread.currentThread();
// 判断是否中断
if(currentThread.isInterrupted()){
log.info("处理后事");
break;
}
try {
TimeUnit.SECONDS.sleep(1);
log.info("日志监控");
} catch (InterruptedException e) {
e.printStackTrace();
// 重新设置中断状态
log.debug("重新设置中断状态");
currentThread.interrupt();
}
}
});
t1.start();
TimeUnit.SECONDS.sleep(4);
t1.interrupt();
}
}
结果如下
注意一下,介绍一下Thread.interrupted(),这个方法会返回当前线程的打断标记,若为true,将值返回后会自动重新刷新为false.
两阶段终止很好的替代了stop()方法.
过时方法
-
stop()
: 停止进程运行,相当于直接杀死进程,这样容易造成死锁,因为可能这个线程拿到了锁的资源,直接杀死导致锁资源没有被释放,别的线程获取不到锁,造成死锁. -
suspend()
: 挂起(暂停)线程运行 -
resume()
: 恢复线程运行后面两个方法被设计用来暂停和恢复线程的执行,但由于它们的使用不当可能导致线程永远挂起
假设有两个线程 A 和 B,线程 A 在执行过程中调用了线程 B 的 suspend() 方法来暂停线程 B,然后线程 A 需要在某个条件满足时恢复线程 B,于是调用了线程 B 的 resume() 方法。如果线程 A 在调用 resume() 方法之前被阻塞或结束了,那么线程 B 将永远被挂起,导致死锁
具体原因
- 死锁风险:如上例所示,使用 suspend() 和 resume() 容易导致线程之间的死锁,特别是在多线程环境下,线程间的执行顺序难以预测和控制。
- 不一致的状态:在线程被 suspend() 暂停时,它可能持有一些重要的资源锁。如果此时其他线程试图访问这些资源,它们将被阻塞,从而导致系统性能下降或死锁。
- 难以调试:使用 suspend() 和 resume() 可能导致线程的行为变得难以预测和调试,特别是在复杂的系统中。