1. 创建线程的四种方式
在Java中,有四种常见的方式可以创建和启动线程:
继承Thread类:
创建一个类继承自
Thread
类,然后重写run()
方法。调用start()
方法启动线程。
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread is running...");
}
}
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // 启动线程
}
}
实现Runnable接口:
创建一个实现
Runnable
接口的类,然后实现run()
方法。将Runnable
对象传递给Thread
对象,并调用start()
方法启动线程。
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread is running...");
}
}
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start(); // 启动线程
}
}
在Java中,Callable
接口是一个功能更强的接口,相比Runnable
接口,它可以返回结果,并且可以抛出受检异常。要使用Callable
接口创建并执行线程,通常与Future
和ExecutorService
一起使用。以下是实现Callable
接口的步骤:
实现
Callable
接口:创建一个类实现
Callable
接口,并实现call()
方法。call()
方法可以返回一个结果,并且允许抛出异常。
import java.util.concurrent.Callable;
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
// 线程执行的任务
int sum = 0;
for (int i = 1; i <= 10; i++) {
sum += i;
}
return sum; // 返回结果
}
}
使用
ExecutorService
执行Callable
任务:使用
ExecutorService
来执行Callable
任务,并获取返回的Future
对象。通过Future
对象的get()
方法,可以获取call()
方法的返回值。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Main {
public static void main(String[] args) {
// 创建一个可重用固定线程池
ExecutorService executor = Executors.newFixedThreadPool(1);
// 提交Callable任务并获取Future对象
Future<Integer> future = executor.submit(new MyCallable());
try {
// 获取线程执行的结果
Integer result = future.get();
System.out.println("Sum: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} finally {
// 关闭ExecutorService
executor.shutdown();
}
}
}
解释:
Callable
接口:类似于Runnable
,但Callable
的call()
方法允许返回一个结果或抛出异常。ExecutorService
:用于管理线程池的接口,submit()
方法可以提交一个Callable
任务。Future
对象:表示异步计算的结果,get()
方法可以获取Callable
的返回值。如果任务尚未完成,get()
将阻塞直到任务完成。
使用场景:
Callable
接口特别适用于需要在线程中执行一些计算或任务,并且希望获取结果的场景。它可以返回任意类型的结果,并且可以在执行任务时处理异常。
2. runnable 和 callable 有什么区别
1. Runnable
接口run方法没有返回值;Callable
接口call
方法有返回值,是个泛
型,和Future、FutureTask配合可以用来获取异步执行的结果
2. Callalbe
接口支持返回执行结果,需要调用FutureTask.get()
得到,此方法会阻
塞主进程的继续往下执行,如果不调用不会阻塞。
3. Callable
接口的call()
方法允许抛出异常;而Runnable
接口的run()
方法的异常
只能在内部消化,不能继续上抛
3. 线程包括哪些状态,状态之间是如何变化的
在Java中,线程的生命周期包括以下几种状态,并且这些状态之间可以通过特定的事件或操作进行转换。这些状态和它们的转换过程如下:
新建状态(New)
线程对象被创建,但还未调用
start()
方法。此时线程处于新建状态。
Thread thread = new Thread(() -> {
// 线程任务
});
就绪状态(Runnable)
当调用
start()
方法后,线程进入就绪状态。此时线程已经准备好等待CPU调度,但还未开始执行。
thread.start();
运行状态(Running)
当线程获得CPU时间片并开始执行其
run()
方法时,线程进入运行状态。在这期间,线程执行它的任务。
public void run() {
// 线程正在执行的任务
}
阻塞状态(Blocked)
当线程等待某个锁资源时(比如试图进入一个被其他线程占用的同步代码块),线程会进入阻塞状态。此时线程处于等待状态,无法执行。
synchronized (lock) {
// 线程需要锁资源才能执行的任务
}
等待状态(Waiting)
线程进入等待状态,通常是由于调用了
Object.wait()
方法、Thread.join()
方法或LockSupport.park()
方法,线程在等待其他线程显式唤醒。
synchronized (lock) {
lock.wait(); // 线程等待某个条件
}
限时等待状态(Timed Waiting)
线程在调用了有超时时间的等待方法时会进入限时等待状态。例如,调用
Thread.sleep(long millis)
、Object.wait(long timeout)
、Thread.join(long millis)
等方法。
Thread.sleep(1000); // 线程休眠1秒钟
终止状态(Terminated)
当线程的
run()
方法执行完
绘制Java线程状态
• New:表示线程被创建后的初始状态。
• Runnable:表示线程就绪,等待CPU调度。
• Running:表示线程正在运行。
• Blocked:表示线程被阻塞,等待锁资源。
• Waiting:表示线程处于等待状态,等待其他线程的通知。
• Timed_Waiting:表示线程在有限时间内处于等待状态。
• Terminated:表示线程已经终止。
4. 在 java 中 wait 和 sleep 方法的不同
共同点
wait() ,wait(long) 和 sleep(long) 的效果都是让当前线程暂时放弃 CPU 的使用权,进入阻塞状态
不同点
方法归属不同
sleep(long) 是 Thread 的静态方法,而 wait(),wait(long) 都是 Object 的成员方法,每个对象都有
醒来时机不同
执行 sleep(long) 和 wait(long) 的线程都会在等待相应毫秒后醒来wait(long) 和 wait() 还可以被 notify 唤醒,wait() 如果不唤醒就一直等下去它们都可以被打断唤醒
锁特性不同(重点)
wait 方法的调用必须先获取 wait 对象的锁,而 sleep 则无此限制,wait 方法执行后会释放对象锁,允许其它线程获得该对象锁(我放弃cpu,但你们还可以用)而 sleep 如果在 synchronized 代码块中执行,并不会释放对象锁(我放弃cpu,你们也用不了)
下面是一个简单的Java代码示例,展示了wait()
和sleep()
的使用方式及它们的不同之处。
wait()
示例
这个示例展示了线程间的通信,一个线程在等待条件满足(通过wait()
),而另一个线程在合适的时机通知它继续执行(通过notify()
)。
class WaitAndNotifyExample {
private static final Object lock = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (lock) {
System.out.println("Thread 1: Waiting for notification...");
try {
lock.wait(); // 线程1等待,释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 1: Notified and resumed!");
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock) {
System.out.println("Thread 2: Doing some work...");
try {
Thread.sleep(2000); // 模拟工作,睡眠2秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 2: Finished work, now notifying...");
lock.notify(); // 通知等待的线程
}
});
thread1.start();
thread2.start();
}
}
运行结果:
Thread 1: Waiting for notification...
Thread 2: Doing some work...
Thread 2: Finished work, now notifying...
Thread 1: Notified and resumed!
解释:
thread1
:调用wait()
方法,进入等待状态并释放锁,直到被通知。thread2
:在完成模拟的工作后调用notify()
方法,通知thread1
恢复执行。
sleep()
示例
这个示例展示了线程在执行过程中暂停一段时间,然后继续执行。
class SleepExample {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("Thread: Doing some work...");
try {
Thread.sleep(3000); // 线程睡眠3秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread: Woke up after sleeping!");
});
thread.start();
}
}
运行结果:
Thread: Doing some work...
Thread: Woke up after sleeping!
解释:
thread
:调用sleep(3000)
方法,线程暂停3秒,然后继续执行其余的任务。 区别总结:在
wait()
示例中,线程thread1
等待另一个线程thread2
的通知(通过notify()
),并在等待期间释放了锁。在
sleep()
示例中,线程只是暂停执行了一段时间,但并未释放任何锁。
Synchronized 关键字的底层原理
synchronized
底层使用的JVM级别中的Monitor
来决定当前线程是否获得了锁,如果某一个线程获得了锁,在没有释放锁之前,其他线程是不能或得到锁的。synchronized
属于悲观锁。
monitor
对象存在于每个Java对象的对象头中,synchronized
锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因