1、Java当中什么是线程和进程
在Java中,线程和进程是两个非常重要的概念。进程可以被视为一个执行中的程序的实例,它拥有自己的内存空间和系统资源。而线程则是进程中的一个实体,由进程创建,并允许程序在同一时刻执行多个任务。Java提供了两种实现多线程的方式:一种是通过继承Thread类并重写run()方法来创建线程;另一种是实现Runnable接口,将逻辑代码写入该接口的实现类后,将这个实现类的实例作为参数传递给Thread类的构造函数,从而创建线程。
以下是一个具体的Java多线程案例代码:
// 任务类
public class MyRunnable implements Runnable {
@Override
public void run () {
for (int i = 1; i <= 5; i++) {
System.out.println("MyRunnable==》" + i);
}
}
}
public class Main {
public static void main(String[] args) {
// 创建线程对象
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
// 启动线程
thread.start();
}
}
2、Java创建线程有哪几种方式
Java创建线程有三种方式:
- 继承Thread类,重写run()方法。
- 实现Runnable接口,将逻辑代码写入该接口的实现类后,将这个实现类的实例作为参数传递给Thread类的构造函数,从而创建线程。
- 实现Callable接口,重写call()方法
以下是两种创建线程的案例代码:
- 继承Thread类,重写run()方法:
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("MyThread==》" + i);
}
}
}
public class Main {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start(); // 启动线程
}
}
- 实现Runnable接口,将逻辑代码写入该接口的实现类后,将这个实现类的实例作为参数传递给Thread类的构造函数,从而创建线程:
public class MyRunnable implements Runnable {
@Override
public void run () {
for (int i = 0; i < 5; i++) {
System.out.println("MyRunnable==》" + i);
}
}
}
public class Main {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable); // 创建线程对象
thread.start(); // 启动线程
}
}
- 实现Callable接口,重写call()方法
Callable:有返回值的线程,能取消线程,可以判断线程是否执行完毕
public class Main {
public static void main(String[] args) throws Exception {
// 将Callable包装成FutureTask,FutureTask也是一种Runnable
MyCallable callable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(callable);
new Thread(futureTask).start();
// get方法会阻塞调用的线程
Integer sum = futureTask.get();
System.out.println(Thread.currentThread().getName() + Thread.currentThread().getId() + "=" + sum);
}
}
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + " \tstarting...");
int sum = 0;
for (int i = 0; i <= 100000; i++) {
sum += i;
}
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName() + "\t" + Thread.currentThread().getId() + "\t" + new Date() + " \tover...");
return sum;
}
}
Callable 也是一种函数式接口
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
FutureTask
public class FutureTask<V> implements RunnableFuture<V> {
// 构造函数
public FutureTask(Callable<V> callable);
// 取消线程
public boolean cancel(boolean mayInterruptIfRunning);
// 判断线程
public boolean isDone();
// 获取线程执行结果
public V get() throws InterruptedException, ExecutionException;
}
RunnableFuture
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
三种方式比较:
- Thread: 继承方式, 不建议使用, 因为Java是单继承的,继承了Thread就没办法继承其它类了,不够灵活
- Runnable: 实现接口,比Thread类更加灵活,没有单继承的限制
- Callable: Thread和Runnable都是重写的run()方法并且没有返回值,Callable是重写的call()方法并且有返回值并可以借助FutureTask类来判断线程是否已经执行完毕或者取消线程执行
当线程不需要返回值时使用Runnable,需要返回值时就使用Callable,一般情况下不直接把线程体代码放到Thread类中,一般通过Thread类来启动线程 - Thread类是实现Runnable,Callable封装成FutureTask,FutureTask实现RunnableFuture,RunnableFuture继承Runnable,所以Callable也算是一种Runnable,所以三种实现方式本质上都是Runnable实现
3、Java线程状态
Java线程状态有以下6种:
- 新建(New):线程对象被创建后,就进入了新建状态。
- 就绪(Runnable):当调用线程的start()方法时,线程进入就绪状态。此时线程已经获取了除CPU资源外的所有资源,只等待CPU资源的分配。
- 运行(Running):当就绪状态的线程获得CPU资源时,线程进入运行状态。此时线程开始执行run()方法中的代码。
- 阻塞(Blocked):线程在运行过程中,可能会因为某些原因而进入阻塞状态。例如,线程调用了sleep()、wait()等方法,或者试图获取一个正在被其他线程持有的锁。
- 等待(Waiting):当线程处于阻塞状态时,如果其他线程调用了该线程的notify()或notifyAll()方法,那么该线程将进入等待状态。等待状态下的线程不会占用CPU资源,直到其他线程再次唤醒它。
- 超时等待(Timed Waiting):与等待状态类似,但超时等待状态下的线程会在指定的超时时间内自动返回到等待队列中。
以下是一个简单的Java多线程案例代码:
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("MyThread==》" + i);
}
}
}
public class Main {
public static void main(String[] args) {
MyThread myThread = new MyThread(); // 新建状态
myThread.start(); // 就绪状态
myThread.run(); // 运行状态
}
}
4、Java线程并行与并发
Java线程的并行和并发是指在一个程序中同时执行多个线程,以提高程序的效率。
并行是指多个线程同时执行,每个线程都有自己的独立的执行路径,互不干扰。
例如,在计算某个问题时,可以将该问题分成若干个子问题,然后创建多个线程分别处理这些子问题,最后将各个子问题的计算结果合并得到最终结果。
并发是指多个线程交替执行,每个线程都会在某些时刻占有CPU资源进行计算。
例如,在处理大量数据时,可以创建一个生产者线程和一个消费者线程,生产者线程负责生成数据,消费者线程负责处理数据,两个线程交替执行,从而提高程序的效率。
1、以下是一个Java多线程并行案例代码:
class MyRunnable implements Runnable {
private String name;
public MyRunnable(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name + "正在执行任务:" + i);
}
}
}
public class MultiThreadDemo {
public static void main(String[] args) {
Thread thread1 = new Thread(new MyRunnable("线程1"));
Thread thread2 = new Thread(new MyRunnable("线程2"));
Thread thread3 = new Thread(new MyRunnable("线程3"));
thread1.start();
thread2.start();
thread3.start();
}
}
在这个例子中,我们创建了一个实现Runnable接口的类MyRunnable,它有一个run方法,用于执行任务。在主函数中,我们创建了三个线程对象,分别传入不同的名称,并将它们启动。这样,这三个线程将并行执行任务。
2、以下是一个Java多线程并发案例代码:
public class MultiThreadDemo {
public static void main(String[] args) {
// 创建两个线程对象
Thread thread1 = new Thread(new MyRunnable(), "线程1");
Thread thread2 = new Thread(new MyRunnable(), "线程2");
// 启动线程
thread1.start();
thread2.start();
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "正在执行任务:" + i);
try {
// 模拟线程执行任务需要的时间
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
在这个例子中,我们创建了一个名为MultiThreadDemo
的类,其中包含一个main
方法。在main
方法中,我们创建了两个线程对象thread1
和thread2
,并将它们分别命名为"线程1"和"线程2"。然后,我们调用start()
方法启动这两个线程。
我们还创建了一个实现Runnable
接口的类MyRunnable
,并在其中定义了run
方法。在run
方法中,我们使用一个循环来模拟线程执行任务的过程。在每次循环中,我们打印出当前线程的名称和任务编号,并让线程休眠1秒钟以模拟任务执行所需的时间。
5、什么是同步执行和异步执行
同步执行和异步执行是Java线程编程中的两种执行方式。
-
同步执行:当一个线程在执行某个方法时,其他线程必须等待该线程执行完毕后才能继续执行。这种执行方式称为同步执行。
-
异步执行:当一个线程在执行某个方法时,其他线程可以同时执行其他任务,而不需要等待该线程执行完毕。这种执行方式称为异步执行。
下面分别给出同步执行和异步执行的示例代码:
同步执行示例代码:
public class SynchronizedExample {
public static void main(String[] args) {
Object lock = new Object();
Thread thread1 = new Thread(() -> {
synchronized (lock) {
System.out.println("线程1开始执行");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1执行完毕");
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock) {
System.out.println("线程2开始执行");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2执行完毕");
}
});
thread1.start();
thread2.start();
}
}
输出结果
线程1开始执行
线程1执行完毕
线程2开始执行
线程2执行完毕
异步执行示例代码:
public class AsynchronousExample {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
System.out.println("线程1开始执行");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1执行完毕");
});
Thread thread2 = new Thread(() -> {
System.out.println("线程2开始执行");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2执行完毕");
});
thread1.start();
thread2.start();
}
}
输出结果
线程1开始执行
线程2开始执行
线程2执行完毕
线程1执行完毕
输出结果
线程1开始执行
线程2开始执行
线程1执行完毕
线程2执行完毕
6、Java多线程操作数据库
在Java中,多线程操作数据库可以通过以下几种方式实现:
- 使用
ExecutorService
和Callable
接口:
ExecutorService是Java中的一个接口,它属于java.util.concurrent包。这个接口表述了异步执行的机制,并且可以让任务在后台执行。ExecutorService实例就像一个线程池,它是线程池的一个定义,并且在这个接口中定义了和后台任务执行相关的方法。
ExecutorService的使用可以带来很多优点。首先,由于请求到达时,线程已经存在,所以响应延迟低。其次,多个任务复用线程,避免了线程的重复创建和销毁,这有助于提高系统的性能。此外,通过使用ExecutorService,我们可以方便地创建多线程执行环境。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class MultiThreadedDatabaseOperation {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);
List<Future<Void>> futures = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Callable<Void> task = () -> {
try {
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "username", "password");
Statement statement = connection.createStatement();
statement.executeUpdate("INSERT INTO table_name (column1, column2) VALUES ('value1', 'value2')");
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
return null;
};
futures.add(executor.submit(task));
}
for (Future<Void> future : futures) {
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
executor.shutdown();
}
}
- 使用
ThreadPoolExecutor
和Runnable
接口:
ThreadPoolExecutor是Java中的一个线程池实现类,它属于java.util.concurrent包。
这个类是线程池中非常关键的一部分,主要用于管理和控制线程的创建与销毁。
使用ThreadPoolExecutor的主要优点在于,它可以避免频繁地创建和销毁线程所带来的开销。当系统中频繁地创建线程时,如果线程过多,会带来调度开销,进而影响缓存局部性和整体性能。
而ThreadPoolExecutor维护着多个线程,等待着监督管理者分配可并发执行的任务。这种方式不仅能够保证内核的充分利用,还能防止过分调度。
通过ThreadPoolExecutor的execute()方法,我们可以执行Runnable任务;
另外,通过ThreadPoolExecutor的submit()方法,我们不仅可以执行Runnable任务,还可以执行Callable任务,并且能够获取异步的执行结果。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.concurrent.*;
public class MultiThreadedDatabaseOperation {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5));
for (int i = 0; i < 10; i++) {
executor.execute(() -> {
try {
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "username", "password");
Statement statement = connection.createStatement();
statement.executeUpdate("INSERT INTO table_name (column1, column2) VALUES ('value1', 'value2')");
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
});
}
executor.shutdown();
}
}
这两种方法都可以实现多线程操作数据库,但第一种方法使用了ExecutorService
和Callable
接口,而第二种方法使用了ThreadPoolExecutor
和Runnable
接口。
你可以根据实际需求选择合适的方法。
7、Java线程池概念和用法
Java线程池是一种管理线程的工具,它可以有效地控制并发线程的数量,避免过多的线程导致系统资源耗尽。
线程池的主要作用是复用已经创建的线程,减少线程创建和销毁的开销。
Java中创建线程池的方式有以下几种:
- 使用
Executors
工厂方法创建固定大小的线程池:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo {
public static void main(String[] args) {
// 创建一个固定大小为5的线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println("线程名:" + Thread.currentThread().getName());
}
});
}
// 关闭线程池
executorService.shutdown();
}
}
- 使用
ThreadPoolExecutor
类创建自定义大小的线程池:
import java.util.concurrent.*;
public class CustomThreadPoolDemo {
public static void main(String[] args) {
// 创建一个自定义大小的线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 5, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10));
for (int i = 0; i < 10; i++) {
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程名:" + Thread.currentThread().getName());
}
});
}
// 关闭线程池
threadPoolExecutor.shutdown();
}
}
new ThreadPoolExecutor(2, 5, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10));
是一个创建线程池的代码片段,其中各个参数的含义如下:
2
:表示线程池的核心线程数(即最小线程数)。当任务数量小于核心线程数时,线程池会创建新的线程来执行任务。5
:表示线程池的最大线程数(即最大线程数)。当任务数量大于核心线程数且小于最大线程数时,线程池会创建新的线程来执行任务。60
:表示线程空闲时间,超过这个时间的线程会被终止。单位是秒。TimeUnit.SECONDS
:表示时间单位是秒。new LinkedBlockingQueue<>(10)
:表示线程池使用一个容量为10的阻塞队列来存储待执行的任务。当队列满时,新提交的任务会被阻塞等待队列中有空闲位置。
综上所述,这段代码创建了一个具有2个核心线程、5个最大线程、线程空闲时间为60秒、使用LinkedBlockingQueue作为任务队列的线程池。
3. 使用ExecutorService
接口的实现类创建线程池:
import java.util.concurrent.*;
public class InterfaceThreadPoolDemo {
public static void main(String[] args) {
// 创建一个固定大小为5的线程池
ExecutorService executorService = new ThreadPoolExecutor(2, 5, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10));
for (int i = 0; i < 10; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程名:" + Thread.currentThread().getName());
}
});
}
// 关闭线程池
executorService.shutdown();
}
}
参数的含义如下:
-
2
:这是核心线程数。线程池中的线程数量至少为这个值。如果任务队列满,那么即使有空闲的线程,也不会创建新的线程来执行任务。 -
5
:这是最大线程数。线程池中的线程数量最多为这个值。如果任务队列满,并且当前线程数已经达到最大线程数,那么新提交的任务会被阻塞,直到有线程完成任务并释放资源。 -
60
:这是线程空闲时间。当线程池中的线程空闲超过这个时间后,这些线程会被终止。 -
TimeUnit.SECONDS
:这是时间单位。在这个例子中,空闲时间是以秒为单位的。 -
new LinkedBlockingQueue<>(10)
:这是任务队列。这是一个阻塞队列,用于存储待执行的任务。当队列满时,新提交的任务会被阻塞等待队列中有空闲位置。
所以,这段代码创建了一个线程池,其核心线程数为2,最大线程数为5,线程空闲时间为60秒,使用LinkedBlockingQueue作为任务队列,队列的最大容量为10。
8、Java线程什么是Callable和Future
Callable和Future是Java中用于处理并发编程的两个接口。
-
Callable:它是一个带有返回值的任务,可以抛出异常。实现Callable接口的类需要重写
call()
方法,该方法包含任务的具体逻辑。当调用call()
方法时,会返回一个Future对象,表示任务的结果。 -
Future:它是一个表示异步计算结果的接口。通过Future对象,可以在计算完成后获取计算结果,或者取消计算。
下面是一个简单的案例代码:
import java.util.concurrent.*;
public class CallableAndFutureExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
Callable<Integer> callableTask = () -> {
int sum = 0;
for (int i = 0; i < 10; i++) {
sum += i;
}
return sum;
};
Future<Integer> future = executorService.submit(callableTask);
try {
Integer result = future.get(); // 获取计算结果,如果计算尚未完成,此方法会阻塞等待
System.out.println("计算结果:" + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} finally {
executorService.shutdown(); // 关闭线程池
}
}
}
在这个例子中,我们创建了一个Callable任务,该任务计算0到9的和。然后,我们将这个任务提交给线程池执行,并通过Future对象获取计算结果。
9、Java线程常用方法
Java线程常用方法有以下几种:
start():
启动线程。run():
线程执行的方法,需要在子类中重写。join():
等待线程执行完毕。sleep():
让当前线程暂停指定的时间。yield():
让当前线程放弃CPU控制权,让其他线程有机会执行。interrupt():
中断线程。isAlive():
判断线程是否存活。getName():
获取线程名称。setName():
设置线程名称。
以下是一个简单的Java线程示例代码:
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "正在运行,i = " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start(); // 启动线程
try {
myThread.join(); // 等待线程执行完毕
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程结束");
}
}
10、什么是线程的上下文切换
线程的上下文切换是指在多线程环境下,CPU从一个线程切换到另一个线程执行的过程。这个过程涉及到保存当前线程的状态(如寄存器值、栈指针等),并加载另一个线程的状态以继续执行。上下文切换会消耗一定的系统资源,因此需要尽量减少不必要的上下文切换。
案例代码:
public class ContextSwitchExample {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("线程1:" + i);
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("线程2:" + i);
}
}
});
t1.start();
t2.start();
}
}
在这个例子中,我们创建了两个线程t1和t2,分别打印0到999的数字。当这两个线程交替执行时,就发生了上下文切换。
11、sleep和wait的区别?
Java中的sleep和wait方法都用于线程的暂停执行,但它们之间有一些区别:
-
sleep方法是Thread类的静态方法,它可以让当前线程暂停指定的毫秒数。
在休眠期间,线程不会释放锁资源。因此,其他线程无法进入同步代码块或方法。 -
wait方法是Object类的实例方法,它可以让当前线程暂停执行,并释放对象的锁资源。
这样,其他线程就可以进入同步代码块或方法。当其他线程调用同一个对象的notify()或notifyAll()方法时,等待的线程会被唤醒并继续执行。
下面是一个简单的案例代码:
public class SleepWaitExample {
public static void main(String[] args) {
Object lock = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
try {
System.out.println("线程1开始执行");
Thread.sleep(3000); // 线程1休眠3秒
System.out.println("线程1结束执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
try {
System.out.println("线程2开始执行");
lock.wait(); // 线程2等待
System.out.println("线程2结束执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t1.start();
try {
Thread.sleep(2000); // 主线程休眠2秒,让线程1先执行
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
在这个例子中,我们创建了两个线程t1和t2。
线程t1使用sleep方法休眠3秒,而线程t2使用wait方法等待。
当线程t1执行完毕后,主线程休眠2秒,
然后启动线程t2。
由于线程t2使用了wait方法,它会释放lock对象的锁资源,使得主线程可以继续执行。当主线程调用lock.notify()
方法时,线程t2会被唤醒并继续执行。
12、什么是对线程优先级
在Java中,线程优先级是一个整数值,范围从1到10。其中,1代表最低优先级,10代表最高优先级,而默认的线程优先级是5。这个优先级只是表示线程执行的先后顺序,但并不能保证高优先级的线程一定会在低优先级的线程前执行。
下面是一个简单的设置线程优先级的案例代码:
public class SimplePriorities extends Thread {
private int countDown = 5;
public void run() {
while(true) {
countDown--;
if(countDown == 0) {
return;
}
}
}
public static void main(String[] args) {
Thread t1 = new SimplePriorities();
t1.setPriority(10); // 设置线程t1的优先级为最高
Thread t2 = new SimplePriorities();
t2.setPriority(1); // 设置线程t2的优先级为最低
t1.start(); // 启动线程t1
t2.start(); // 启动线程t2
}
}
13、什么是对线程优先级
在Java中,后台线程也被称为守护线程(Daemon Thread),它是一种在后台提供公共服务的线程,通常用于执行一些周期性或支持性任务。
例如,Java的垃圾回收器就是一个典型的守护线程,它在程序运行过程中自动回收不再使用的内存,无需程序员干预。
后台线程的生命周期并不取决于它自身,而是由所有非后台线程(也称为前台线程)决定的。当所有的非后台线程结束时,程序也就终止了,同时会杀死进程中所有的后台线程。
因此,只要有任何非后台线程还在运行,程序就不会结束。
创建后台线程的方式是将目标线程对象的 setDaemon() 方法设置为 true。
以下是一个简单的案例代码:
public class SimplePriorities extends Thread {
private int countDown = 5;
public void run() {
while(true) {
countDown--;
if(countDown == 0) {
return;
}
}
}
public static void main(String[] args) {
Thread t1 = new SimplePriorities();
t1.setDaemon(true); // 设置线程t1为后台线程
Thread t2 = new SimplePriorities();
t2.setDaemon(true); // 设置线程t2为后台线程
t1.start(); // 启动线程t1
t2.start(); // 启动线程t2
try {
TimeUnit.MILLISECONDS.sleep(175); // 主线程睡眠175毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("所有后台线程都已经结束"); // 主线程打印信息
}
}
14、sleep,yiled,wait,join 对比
Java 中的 sleep()
, yield()
, wait()
和 join()
都是多线程中常用的控制线程的方法,它们分别用于不同的目的。以下是对这些方法的简要对比以及使用它们的示例代码。
Thread.sleep(long millis)
: 这个方法使当前线程暂停指定的时间(毫秒)。它不释放任何锁,并且不会抛出 InterruptedException。sleep()
是一个静态方法,属于java.lang.Thread
类。
public class SleepExample {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("Sleeping...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Woke up after sleeping");
}
});
thread.start();
thread.join(); // 等待子线程执行完毕
}
}
Thread.yield()
: 这个方法暗示当前线程应该放弃 CPU 执行时间给其他优先级相同的线程。
但是这个操作是不可靠的,因为 Java 虚拟机可以忽略这个请求。
yield()
是一个静态方法,属于java.lang.Thread
类。
public class YieldExample {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
while (true) {
System.out.println("Thread 1 running");
Thread.yield();
}
});
Thread thread2 = new Thread(() -> {
while (true) {
System.out.println("Thread 2 running");
Thread.yield();
}
});
thread1.start();
thread2.start();
}
}
Object.wait()
: 这个方法使当前线程等待直到另一个线程调用此对象的notify()
或notifyAll()
方法,或者超过指定的等待时间。
该方法必须在同步块或同步方法中调用,否则会抛出IllegalMonitorStateException
。wait()
是一个实例方法,属于java.lang.Object
类。
public class WaitExample {
private final Object lock = new Object();
public static void main(String[] args) {
WaitExample example = new WaitExample();
example.run();
}
public void run() {
Thread producer = new Thread(() -> {
while (true) {
synchronized (lock) {
System.out.println("Producing");
try {
lock.wait(); // 等待消费者消费
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread consumer = new Thread(() -> {
while (true) {
synchronized (lock) {
System.out.println("Consuming");
lock.notify(); // 唤醒生产者
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
producer.start();
consumer.start();
}
}
Thread.join()
: 这个方法使当前线程等待目标线程终止。如果两个线程都在同一个进程中运行,那么当其中一个线程结束时,另一个线程就可以继续执行。
join()
是一个实例方法,属于java.lang.Thread
类。
public class JoinExample {
public static void main(String[] args) {
Thread worker = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("Worker is working on task " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
worker.start();
try {
worker.join(); // 等待工人线程完成工作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Main thread can continue now that the worker is done.");
}
}
以上就是对 Java 中 sleep()
, yield()
, wait()
和 join()
的简单介绍和使用示例。
请注意,在实际编程中,需要根据具体需求选择合适的方法来控制线程行为。