本文主要介绍Java中关于线程同步的一些基本概念和方法,包括sleep()和wait()的用法和区别。
首先,Java中的wait和sleep都是用来控制线程的执行的方法,但是它们的使用场景和底层实现有很大的区别。
sleep
sleep的作用是让线程休眠指定时间,然后重新进入就绪状态。sleep可以在任何地方使用,不需要获取锁。sleep的使用场景主要是控制线程执行的时间,比如实现定时任务。
public static void sleep(long millis) throws InterruptedException {
Thread.sleep(millis);
}
wait
wait的作用是让线程等待,直到其他线程调用notify或notifyAll方法,使该线程重新进入就绪状态,唤醒它,或者等待时间结束。wait通常和synchronized关键字一起使用,用于线程间的协调和通信。wait必须在synchronized块中调用,否则会抛出IllegalMonitorStateException异常。wait的使用场景主要是在线程间进行通信,等待某个条件的满足。
public static void wait(long millis) throws InterruptedException {
Thread.sleep(millis);
synchronized (this) {
while (!isDone) {
wait(0);
}
}
}
区别
两个方法的区别在于,sleep()方法会释放线程的锁,而wait()方法不会。因此,当一个线程想要等待另一个线程完成某个操作时,应该使用wait()方法,而当一个线程想要暂停一段时间时,应该使用sleep()方法。
存在的问题
wait和sleep方法都存在一些问题:
- wait方法容易造成死锁。如果一个线程在等待另一个线程唤醒它,而另一个线程正好在等待它释放锁,就会出现死锁。
- sleep方法不能保证精确的休眠时间,因为线程进入休眠状态后,可能会被其他线程唤醒或者中断。
- wait和sleep方法都会使线程进入阻塞状态,如果不恰当地使用,容易导致线程饥饿、死锁等问题。
至于如何高效地使用阻塞, 更多可以参考下方的拓展阅读,比如说使用TimeUnit.SECONDS.sleep("你的暂停时间");
或者利用组件ScheduledExecutorService来实现定时操作。
ScheduledExecutorService
ScheduledExecutorService是Java中的一个线程池,用于执行定时任务或周期性任务。它继承了ExecutorService接口,可以使用submit()方法提交任务,并使用get()方法获取任务的Future对象,以便获取任务的执行结果。
我们以代码实例进行说明,比方说我们要实现一个轮询操作
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledExecutorServiceExample {
public static void main(String[] args) {
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("Task executed");
}
};
executorService.scheduleAtFixedRate(task, 5, 10, TimeUnit.SECONDS);
}
}
上述代码创建了一个ScheduledExecutorService对象,并使用scheduleAtFixedRate()方法提交了一个定时任务。该任务每隔10秒钟执行一次,一共执行5次。
在任务执行时,我们打印了一条消息,表示任务已经被执行。 需要注意的是,ScheduledExecutorService中的任务是线程安全的,因此可以在多个线程之间共享同一个ScheduledExecutorService对象。此外,ScheduledExecutorService还支持延迟时间和周期执行任务的方式,可以根据实际需求选择合适的方式来提交任务。
另外,虽然这个Service可以实现多线程共享,但是在线程较多时难免会产生竞态条件,此时我们可以为每个线程创建一个独立的Service对象。
参考资料