文章内容上次编辑时间于 7 months ago。文章内容已经很陈旧了,也许不再适用!
文章共 1880 字,阅读完预计需要 3 分钟 8 秒。文章篇幅适中,可以放心阅读。

1. 创建线程的四种方式

在Java中,有四种常见的方式可以创建和启动线程:

  1. 继承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(); // 启动线程
}
}
  1. 实现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接口创建并执行线程,通常与FutureExecutorService一起使用。以下是实现Callable接口的步骤:

  1. 实现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; // 返回结果
}
}
  1. 使用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,但Callablecall()方法允许返回一个结果或抛出异常。

  • 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中,线程的生命周期包括以下几种状态,并且这些状态之间可以通过特定的事件或操作进行转换。这些状态和它们的转换过程如下:

  1. 新建状态(New)

    • 线程对象被创建,但还未调用start()方法。此时线程处于新建状态。

Thread thread = new Thread(() -> {
// 线程任务
});
  1. 就绪状态(Runnable)

    • 当调用start()方法后,线程进入就绪状态。此时线程已经准备好等待CPU调度,但还未开始执行。

   thread.start();
  1. 运行状态(Running)

    • 当线程获得CPU时间片并开始执行其run()方法时,线程进入运行状态。在这期间,线程执行它的任务。

public void run() {
// 线程正在执行的任务
}
  1. 阻塞状态(Blocked)

    • 当线程等待某个锁资源时(比如试图进入一个被其他线程占用的同步代码块),线程会进入阻塞状态。此时线程处于等待状态,无法执行。

synchronized (lock) {
// 线程需要锁资源才能执行的任务
}
  1. 等待状态(Waiting)

    • 线程进入等待状态,通常是由于调用了Object.wait()方法、Thread.join()方法或LockSupport.park()方法,线程在等待其他线程显式唤醒。

synchronized (lock) {
lock.wait(); // 线程等待某个条件
}
  1. 限时等待状态(Timed Waiting)

    • 线程在调用了有超时时间的等待方法时会进入限时等待状态。例如,调用Thread.sleep(long millis)Object.wait(long timeout)Thread.join(long millis)等方法。

   Thread.sleep(1000);  // 线程休眠1秒钟
  1. 终止状态(Terminated)

    • 当线程的run()方法执行完

绘制Java线程状态

创建线程
调用 start()
调度获取CPU时间片
yield() 让出CPU时间片
等待获取锁资源
获得锁资源
调用 wait(), join(), LockSupport.park()
notify(), notifyAll(), join 线程结束
sleep(), wait(timeout), join(timeout)
超时或被唤醒
run()结束或抛出异常
wait(timeout), join(timeout)
New
Runnable
Running
Blocked
Waiting
Timed_Waiting
Terminated

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()的使用方式及它们的不同之处。

  1. 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恢复执行。

  1. 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中任意对象可以作为锁的原因

Switch Theme | SCHEME TOOL