创建方法
1.继承Thread类
public class MyThread extends Thread {
private int counter;
public MyThread(int counter) {
this.counter = counter;
}
@Override
public void run() {
while (counter > 0) {
System.out.println("Thread " + Thread.currentThread().getName() + ": " + counter);
counter--;
}
}
}
public class Main {
public static void main(String[] args) {
MyThread t1 = new MyThread(5);
MyThread t2 = new MyThread(5);
t1.start();
t2.start();
}
}
2.实现Runnable接口
public class MyRunnable implements Runnable {
private int counter;
public MyRunnable(int counter) {
this.counter = counter;
}
@Override
public void run() {
while (counter > 0) {
System.out.println("Thread " + Thread.currentThread().getName() + ": " + counter);
counter--;
}
}
}
public class Main {
public static void main(String[] args) {
MyRunnable r = new MyRunnable(5);
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
}
}
3.实现Callable接口
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
}
public static void main(String[] args) throws Exception {
MyCallable callable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(callable);
Thread t = new Thread(futureTask);
t.start();
System.out.println(futureTask.get());
}
}
Callable和Runnable的区别
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1.使用Callable实现多线程
Callable<Integer> task = () -> {
int sum = 0;
for (int i = 0; i < 10; i++) {
sum += i;
}
return sum;
};
FutureTask<Integer> futureTask = new FutureTask<>(task);
Thread thread = new Thread(futureTask);
thread.start();
int result = futureTask.get(); // 阻塞等待任务执行完毕并返回结果
System.out.println(result);
// 2.A 使用Runnable实现多线程
Runnable runnable = () -> {
int sum = 0;
for (int i = 0; i < 10; i++) {
sum += i;
}
System.out.println(sum);
};
Thread thread1 = new Thread(runnable);
thread1.start();
// 2.B FutureTask包装Runnable任务
Runnable runnable = () -> {
int sum = 0;
for (int i = 0; i < 10; i++) {
sum += i;
}
System.out.println(sum);
};
FutureTask<Void> futureTask = new FutureTask<>(runnable, null);
Thread thread = new Thread(futureTask);
thread.start();
}
}
Callable和Runnable都是Java中用于实现多线程的接口,它们的最大的区别在于:
- 返回值:Runnable的run方法没有返回值,而Callable的call方法有返回值,并且可以通过Future接口来获取返回值。
- 异常处理:Runnable的run方法不能抛出受检查的异常,而Callable的call方法可以抛出异常,需要通过try-catch语句来处理。
4.线程池
1.固定大小线程池FixedThreadPool
:创建一个固定大小的线程池,线程数不变,当有一个新任务提交时,线程池中若有空闲线程,则立即执行,否则就会被暂存在一个任务队列中,等待有线程空闲时再去执行。
javaCopy codeimport java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FixedThreadPoolExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
for (int i = 1; i <= 5; i++) {
executorService.execute(new Task(i));
}
executorService.shutdown();
}
static class Task implements Runnable {
private int taskId;
public Task(int taskId) {
this.taskId = taskId;
}
@Override
public void run() {
System.out.println("Task #" + this.taskId + " is running.");
}
}
}
这里创建了一个固定大小为2的线程池,提交5个任务给线程池,因为线程池大小为2,所以会有2个线程一直在运行这些任务,直到所有任务都执行完毕.
2.缓存线程池CachedThreadPool
:创建一个可缓存的线程池,线程数根据需要自动增加,但是在之后的空闲时间里面自动减少线程数量,适用于执行很多短期异步任务的程序,可以减少创建线程的时间开销。
javaCopy codeimport java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CachedThreadPoolExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 1; i <= 5; i++) {
executorService.execute(new Task(i));
}
executorService.shutdown();
}
static class Task implements Runnable {
private int taskId;
public Task(int taskId) {
this.taskId = taskId;
}
@Override
public void run() {
System.out.println("Task #" + this.taskId + " is running.");
}
}
}
这里创建了一个缓存线程池,没有限制线程池的大小,根据任务的多少来决定线程池的大小,如果线程池中没有空闲的线程,则会创建一个新的线程来处理任务,如果有空闲的线程,则会使用空闲线程来处理任务。
3.单线程池SingleThreadExecutor
javaCopy codeimport java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingleThreadExecutorExample {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 1; i <= 5; i++) {
executorService.execute(new Task(i));
}
executorService.shutdown();
}
static class Task implements Runnable {
private int taskId;
public Task(int taskId) {
this.taskId = taskId;
}
@Override
public void run() {
System.out.println("Task #" + this.taskId + " is running.");
}
}
}
这里创建了一个单线程池,线程池中只有一个线程来处理任务,按照任务的顺序依次处理每个任务,如果当前任务还未执行完毕,则后面的任务需要等待前面的任务执行完毕后才能执行。
ScheduledThreadPool
:创建一个定长的线程池,支持定时及周期性任务执行,类似于Timer,但是比Timer更强大、更灵活。
javaCopy codeimport java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args) {
// 创建 ScheduledThreadPool 实例,其中线程池大小为 2
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
// 延迟 1 秒后执行任务
executor.schedule(() -> {
System.out.println("Task 1");
}, 1, TimeUnit.SECONDS);
// 延迟 2 秒后执行任务,每隔 1 秒重复执行一次
executor.scheduleAtFixedRate(() -> {
System.out.println("Task 2");java
}, 2, 1, TimeUnit.SECONDS);
// 关闭线程池
executor.shutdown();
}
}
在上面的示例中,我们创建了一个大小为 2 的 ScheduledThreadPool
实例,然后使用 schedule()
和 scheduleAtFixedRate()
方法分别创建了两个不同的定时任务。最后调用 shutdown()
方法关闭线程池。
生命周期
线程的生命周期指的是线程从创建到销毁的整个过程,通常包括以下几个阶段:
- 新建状态(New):当线程对象创建后,它就处于新建状态。此时它并没有开始运行,只是程序中的一个普通对象。
- 就绪状态(Runnable):当线程处于就绪状态时,表示它已经准备好了,只等待分配 CPU 时间片,让它运行。当一个线程被创建后,它首先进入就绪状态。
- 运行状态(Running):当线程获得 CPU 时间片并开始执行时,它就进入了运行状态。
- 阻塞状态(Blocked):当线程因为某些原因无法继续运行时,就会进入阻塞状态。例如等待某个资源或等待输入输出完成。当阻塞状态结束后,线程重新进入就绪状态。
- 等待状态(Waiting):当线程执行 wait()、join() 或 sleep() 方法时,它会进入等待状态。这些方法通常是用来让线程暂停执行一段时间或等待某些条件的满足。
- 超时等待状态(Timed Waiting):当线程执行 sleep()、join() 或 wait(timeout) 方法时,它会进入超时等待状态。这个状态与等待状态类似,只是会等待一段时间后自动返回。
- 终止状态(Terminated):当线程执行完 run() 方法后,它就进入了终止状态。此时线程已经结束了它的生命周期,将无法再次运行。
等待和超时等待是线程在阻塞状态下的特殊情况,具体说,等待和超时等待属于阻塞状态的一种,但是它们不同于一般的阻塞状态,因为它们是主动被动态的进入的。
阻塞状态是指线程因为无法获取到系统资源(如IO操作,获取锁等)而暂停执行,等待系统资源的释放,处于阻塞状态的线程不能执行任何代码。而等待和超时等待则是调用了Object类的wait()和wait(long timeout)方法,主动放弃当前线程持有的锁,并等待其他线程通过notify()或notifyAll()唤醒当前线程。这两个方法被调用时,线程会进入等待状态,并处于等待队列中,直到被唤醒。
因此,等待和超时等待也属于阻塞状态的一种,但是和普通的阻塞状态有区别。
锁机制
锁发展
- 早期的 synchronized 关键字
在JDK1.0中,Java只提供了 synchronized 关键字作为锁机制,它是一种重量级锁(也称作内置锁或者监视器锁),在Java中的每个对象都有一个监视器,一个线程在获取该对象的监视器后,就能够进入这个对象的临界区,其他线程在获取这个对象的监视器时,就只能等待该线程释放锁。synchronized 是一种非常简单、易用的锁机制,但是由于它是重量级锁,会导致性能问题,因此在高并发场景下,性能表现不佳。
- JDK1.5 中的并发包
在JDK1.5中,Java提供了新的并发包 java.util.concurrent,它引入了一些新的锁机制,如 ReentrantLock、ReentrantReadWriteLock、StampedLock、Condition等,这些锁机制相比 synchronized 更加灵活、可控、高效。
- JDK1.6 中的 ConcurrentHashMap
在JDK1.6中,Java提供了 ConcurrentHashMap,这是一种高效的并发 HashMap,它使用了分段锁(也称作细粒度锁),将整个Map分成了多个段,每个段有自己的锁,当多个线程在操作不同的段时,它们之间不会产生竞争,从而提高了并发性能。
- JDK1.8 中的 synchronized 和 Lock 的优化
在JDK1.8中,Java对 synchronized 和 Lock 两种锁机制做了优化,synchronized 引入了偏向锁、轻量级锁、重量级锁等,Lock 引入了公平锁、非公平锁等。这些优化可以提高锁机制的性能和吞吐量。
总体来说,Java的锁机制发展经历了从简单到复杂、从重量级到轻量级、从单一到多样化的过程,不断地为开发者提供更加灵活、高效的锁机制,以满足不同场景下的需求。