1.sleep与wait区别
在多线程编程中,正确理解和使用线程控制方法如sleep和wait是至关重要的。它们的功能似乎有点相似,因为两者都能暂停线程的执行,但实际上它们的作用和应用场景大相径庭。
1.1. 方法的定义所属类别
sleep方法定义在Thread类中,它是一个静态方法,其作用是使当前正在执行的线程停止执行指定的时间段,毫无疑问,这是等待当前线程。
Thread.sleep(long millis);
相比之下,wait是Object类的方法,它的使用必须在同步代码块或同步方法中,通过调用某个对象的wait方法来使得当前线程等待,并释放该对象的锁。
object.wait();
object.wait(long timeout);
1.2. 控制的线程状态
sleep方法使线程从Running状态转到Timed Waiting状态,并在指定的时间后自动回到Runnable状态。
而wait则是将线程从Running状态转到Waiting状态,或者在指定时间后进入Timed Waiting状态。与sleep不同的是,wait控制的线程需要通过notify或notifyAll方法来唤醒。
1.3. 持有锁的影响
执行sleep方法的线程不会释放任何锁。
而执行wait的线程会释放其持有的对象锁,这是为了让其他线程有机会进入同步代码块,对对象进行修改,然后通过notify方法告知原线程对象已经被修改。
1.4. 唤醒条件
sleep无需外界干预,在设定时间结束后会自动唤醒。
wait则在以下三种情况会被唤醒:
其他线程调用了同一个对象的notify()方法,并且当前线程被选为被唤醒的线程。
其他线程调用了同一个对象的notifyAll()方法。
wait设置的等待时间已到。
1.5. 应用场景对比
sleep通常用于暂停执行一段时间,它不涉及对象锁,也不依赖于其他线程的通知来唤醒。
相对于sleep,wait被用于线程间的协作。当一个线程需要等待另一个线程的通知时,或者说一个线程在继续执行前必须要等待某个条件的满足,那它就应该使用wait。
1.6. 实践案例
让我们通过一段代码来演示sleep和wait的实际应用。
public class SleepWaitExample {
private static final Object LOCK = new Object();
public static void main(String[] args) throws InterruptedException {
Thread sleepThread = new Thread(() -> {
System.out.println("Sleep Thread sleeping for 2s.");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Sleep Thread woke up and finished.");
});
Thread waitThread = new Thread(() -> {
synchronized (LOCK) {
try {
System.out.println("Wait Thread waiting on LOCK.");
LOCK.wait();
System.out.println("Wait Thread woke up and finished.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
sleepThread.start();
waitThread.start();
// 主线程故意延迟启动,确保上面的线程执行相关的sleep和wait操作
Thread.sleep(500);
synchronized (LOCK) {
System.out.println("Main Thread calling notify on LOCK.");
LOCK.notify(); // 注意,如果没有这行代码,waitThread将永远处于等待状态
}
}
}
在上面的代码中,我们建立了两个线程。sleepThread通过执行sleep方法暂停两秒钟。与此同时,waitThread通过调用LOCK.wait()暂停并释放了LOCK对象上的锁。主线程随后通过LOCK.notify()唤醒正在等待的waitThread。
这段代码说明了sleep和wait的区别:sleepThread在睡眠期间仍然持有任何已经拥有的锁,而waitThread在等待期间释放了LOCK上的锁。这允许其他持有同一对象锁想进入同步块的线程有机会执行。
2.start与run区别
在Java中,Thread类提供了start()和run()两种启动线程的方法,它们在功能上有本质的区别。
2.1. 方法的调用源与功能
start()方法是Thread类的一个实例方法,调用这个方法会启动一个新线程,并执行该线程的run()方法。
Thread thread = new Thread(() -> {
// 线程执行的代码
});
thread.start(); // 正确启动一个线程的方式
run()方法也是Thread类的一个实例方法,但它只是普通的方法调用,并不会启动新线程。
thread.run(); // 这只会在当前线程中执行run方法的内容,并不会启动新的线程
2.2. 线程启动机制的内部原理
当调用线程对象的start()方法时,会为该线程分配必要的系统资源,并调用操作系统的线程执行功能,最终达到并发执行的效果。
而调用run()方法,则相当于在当前线程中直接执行一个方法调用,它并不会创建新线程,也就不会产生并行的效果。
2.3. 直接调用run方法的结果
直接调用run()方法,线程对象所封装的任务会在调用run()方法的当前线程中直接执行,而不会有线程调度操作。
2.4. 使用start方法的正确姿势
正确使用start()方法,可以实现对线程生命周期的管理,并且允许系统进行调度,如下所示:
Thread thread = new Thread(() -> {
// 线程执行的代码
});
thread.start(); // 这会创建一个新线程,并在这个新线程中执行run方法的代码
2.5. 实战演示
让我们通过一个简单的例子来对比start()和run()的不同效果:
public class ThreadStartRunExample {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("Inside the thread: " + Thread.currentThread().getName());
});
System.out.println("Before start(): " + Thread.currentThread().getName());
// 使用start()方法启动线程
thread.start();
System.out.println("After start(): " + Thread.currentThread().getName());
// 增加一些间隔,确保线程启动后执行
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Before run(): " + Thread.currentThread().getName());
// 直接调用run()方法
thread.run();
System.out.println("After run(): " + Thread.currentThread().getName());
}
}
以上代码执行结果将展示调用start()和直接调用run()的不同。当使用start()时,输出将在一个名为Thread-0的新线程中生成。而直接调用run()时,输出将在主线程中生成。
3.JAVA后台线程(Daemon Thread)
Java中的后台线程(也称守护线程)是程序运行时在后台提供一种通用服务的线程,这种线程并不属于程序中不可或缺的部分。因此,当所有的用户线程(非后台线程)终止时,程序也就终止了,同时会杀死所有剩余的守护线程。
3.1. 后台线程的特性
后台线程最大的特点是它不阻止JVM的终止。无论其执行代码是否运行完毕,一旦所有用户线程都结束,后台线程会自动终止。
3.2. 创建后台线程的方法
要创建一个守护线程,可以通过调用Thread对象的setDaemon(true)方法来设置线程为守护线程。
Thread daemonThread = new Thread(() -> {
// 守护线程执行的代码
});
daemonThread.setDaemon(true); // 将线程设置为守护线程
daemonThread.start(); // 启动守护线程
请注意,一旦启动了线程(即调用了start()方法),再尝试设置它为守护线程会抛出IllegalThreadStateException。
3.3. 主线程与后台线程的生命周期
守护线程的生命周期取决于创建它的用户线程。当最后一个非守护线程结束时,守护线程由JVM自动终止。
3.4. 前台线程与后台线程的对比
用户线程可以认为是程序的工作线程,而守护线程通常是在执行如日志记录、性能监控等服务性任务。用户线程终止其运行时不会考虑守护线程是否完成,直接退出JVM。
3.5. 后台线程的用例
以下示例展示了如何创建守护线程并观察其行为:
public class DaemonThreadExample {
public static void main(String[] args) throws InterruptedException {
Thread daemonThread = new Thread(() -> {
while (true) {
try {
System.out.println("Daemon Thread is running.");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
daemonThread.setDaemon(true);
daemonThread.start();
System.out.println("Main thread is sleeping for 3s.");
Thread.sleep(3000);
System.out.println("Main thread has finished.");
// 当主线程结束后,守护线程也随之结束,即使它的循环并未完成
}
}
在上述代码中,我们创建了一个无限循环的守护线程,并且主线程在休眠3秒后结束。即便守护线程的while循环并没有结束,但它会随着主线程的结束而立即终止。