👏作者简介:大家好,我是若明天不见,BAT的Java高级开发工程师,CSDN博客专家,后端领域优质创作者
📕系列专栏:多线程及高并发系列
📕其他专栏:微服务框架系列、MySQL系列、Redis系列、Leetcode算法系列、GraphQL系列
📜如果感觉博主的文章还不错的话,请👍点赞收藏关注👍支持一下博主哦??
?时间是条环形跑道,万物终将归零,亦得以圆全完美
多线程及高并发系列
线程(Thread):线程是进程内的执行单元,它是操作系统调度的最小单位。一个进程可以包含多个线程,它们共享进程的资源。线程之间可以并发执行,共享内存空间,因此可以更高效地完成多个任务。Java 中的线程由 Java 虚拟机(JVM)进行管理和调度
多线程的作用:
多线程编程也带来了一些挑战,例如线程安全性、数据同步和共享资源管理等问题。Java 提供了丰富的并发编程工具和库,如线程类(Thread)、线程池(ThreadPoolExecutor)、锁(Lock)、原子类(Atomic)等,帮助开发人员更方便地进行多线程编程。
Java.Lang.Thread
类的每个实例,都是一个平台线程,是 Java 对操作系统线程的包装,与操作系统是 1:1 映射java.lang.VirtualThread
这个类虚拟线程(Virtual Thread)它不与特定的操作系统线程相绑定。它在平台线程上运行 Java 代码,但在代码的整个生命周期内不独占平台线程。**这意味着许多虚拟线程可以在同一个平台线程上运行他们的 Java 代码,共享同一个平台线程。详见虚拟线程原理及性能分析
Java中有三种常见的线程使用方式:
实现
Runnable
和Callable
接口的类实际上是任务,最后还需要通过Thread
来执行
创建一个继承自Thread
类的子类,重写其run()
方法。然后可以创建该子类的实例,并调用start()
方法来启动线程
这种方式适合简单的线程任务,不需要额外的线程控制
public class MyThread extends Thread {
public void run() {
// 线程执行的代码
}
}
public class Main {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
创建一个实现了Runnable
接口的类,重写其run()
方法。然后可以创建一个Thread
对象,将该实现类的实例作为参数传递给Thread
构造函数。最后调用Thread
对象的start()
方法来启动线程
这种方式适合于需要执行的任务不需要返回结果的情况
public class MyRunnable implements Runnable {
public void run() {
// 线程执行的代码
}
}
public class Main {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
}
}
创建一个实现了Callable
接口的类,重写其call()
方法。然后可以创建一个FutureTask
对象,将该实现类的实例作为参数传递给FutureTask
构造函数,最后通过FutureTask
对象可以获取任务执行的结果
这种方式适合需要执行任务并且获取返回结果的情况
public class MyCallable implements Callable<String> {
public String call() {
// 线程执行的代码
return "Hello, World!";
}
}
public class Main {
public static void main(String[] args) throws InterruptedException, ExecutionException {
MyCallable myCallable = new MyCallable();
FutureTask<String> futureTask = new FutureTask<>(myCallable);
Thread thread = new Thread(futureTask);
thread.start();
System.out.println(futureTask.get());
}
}
在 Java 中,Thread 类提供了方法及工具来控制线程的行为
setDaemon(boolean on)
、yield()
和sleep(long millis)
interrupted()
synchronized
、ReentranLock
join()
、wait()
、notify()
和notifyAll()
作用:将线程设置为守护线程(daemon thread)或用户线程(user thread)
public class DaemonThreadExample {
public static void main(String[] args) {
Thread daemonThread = new Thread(() -> {
while (true) {
System.out.println("Daemon Thread is running");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
daemonThread.setDaemon(true); // 设置为守护线程
daemonThread.start();
System.out.println("Main Thread is exiting");
}
}
守护线程
daemonThread
,它会不停地输出一条消息。直到主线程(即 main 方法)退出时,守护线程也会随之自动结束
作用:提示调度器当前线程愿意放弃当前的 CPU 时间片,让其他具有相同优先级的线程执行。
调用yield()
方法不会导致线程进入阻塞状态,而是将线程从运行状态转换为就绪状态
yield()
方法是对线程调度器的一个建议,它在一定程度上提高了线程之间的公平性
public class YieldExample {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
System.out.println("Thread 1: " + i);
Thread.yield();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
System.out.println("Thread 2: " + i);
Thread.yield();
}
});
thread1.start();
thread2.start();
}
}
尽可能交替执行输出,但不保证,因为
yield()
的线程也可能又被CPU调度
作用:使当前线程暂停执行指定的时间,进入阻塞状态。
参数:millis
指定线程休眠的时间(以毫秒为单位)
sleep()
方法会暂时释放 CPU,使得其他线程有机会执行。在指定的时间过去后,线程会重新进入就绪状态,等待重新获得 CPU 执行
public class SleepExample {
public static void main(String[] args) {
System.out.println("Before sleep");
try {
Thread.sleep(3000); // 休眠 3 秒钟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("After sleep");
}
}
Java 提供了 Thread 类的interrupt()
方法来中断线程的执行。
调用interrupt()
方法会将线程的中断标志位设置为true
sleep()
、wait()
或join()
方法),会抛出InterruptedException
异常并清除中断标志位作用:静态方法,检查当前线程的中断状态,并重置中断标志位。通常在当前线程需要处理中断状态时使用
public class InterruptExample {
private static class MyThread2 extends Thread {
@Override
public void run() {
while (!interrupted()) {
// ..
}
System.out.println("Thread end");
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread2 = new MyThread2();
thread2.start();
thread2.interrupt();
}
}
如果一个线程的run()
方法执行一个无限循环,并且没有执行sleep()
等会抛出 InterruptedException 的操作,那么调用线程的interrupt()
方法就无法使线程提前结束。此时需要interrupted()
方法来判断线程是否处于中断状态,从而提前结束线程
Java 中的synchronized
关键字用于实现线程同步,确保在同一时间只有一个线程可以进入被 synchronized 修饰的方法或代码块,从而防止多个线程同时访问共享资源,避免出现数据竞争和并发访问的问题
synchronized
原理分析、锁位置、锁状态及锁升级详解见【多线程及高并发 二】volatile & synchorized 详解
// 关键字在实例方法上,锁为当前实例
public synchronized void instanceLock() {
// code
}
// 关键字在静态方法上,锁为当前Class对象
public static synchronized void classLock() {
// code
}
// 关键字在代码块上,锁为括号里面的对象
public void blockLock() {
Object o = new Object();
synchronized (o) {
// code
}
}
synchronized
关键字在JDK 1.5 前本质上是一把悲观锁。JDK 1.5 之后进行了优化,引入了锁升级的概念。在多线程竞争不激烈的情况下,锁会从无锁状态逐渐升级为偏向锁、轻量级锁,最后升级为重量级锁,以提高性能
ReentrantLock
是 Java 提供的可重入锁(Reentrant Lock)实现,它是在java.util.concurrent.locks
包中的一个类。与传统的synchronized
关键字相比,ReentrantLock
提供了更多可编程的灵活性和功能,例如可重入性、公平性、条件变量和更精细的线程控制
lock()
方法进行加锁,使用unlock()
方法进行解锁Condition
条件变量:ReentrantLock 提供了与 CONDITION 相关联的条件变量,用于实现更复杂的线程通信和同步import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private ReentrantLock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private int count = 0;
public void increment() {
lock.lock(); // 加锁
try {
count++;
System.out.println("Incremented: " + count);
condition.signalAll(); // 唤醒等待的线程
} finally {
lock.unlock(); // 解锁
}
}
public void decrement() throws InterruptedException {
lock.lock(); // 加锁
try {
while (count == 0) {
condition.await(); // 等待条件满足
}
count--;
System.out.println("Decremented: " + count);
} finally {
lock.unlock(); // 解锁
}
}
public static void main(String[] args) {
ReentrantLockExample example = new ReentrantLockExample();
Thread incrementThread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
example.increment();
}
});
Thread decrementThread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
example.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
incrementThread.start();
decrementThread.start();
}
}
在上述示例中创建了一个ReentrantLock
对象lock
和一个与之关联的Condition
对象condition
increment()
方法使用lock
加锁,递增count
的值,并唤醒等待的线程decrement()
方法使用lock
加锁,当count
为 0 时,调用condition.await()
方法等待条件满足,否则递减count
的值在Java中,Thread
类提供了几个用于线程间协作的方法,包括join()
、wait()
、notify()
和notifyAll()
。这些方法用于实现线程的等待、唤醒和协调操作
Thread 类的
wait()
、notify()
和notifyAll()
方法是与对象的监视器(monitor)相关联的,而不是直接与 Thread 类相关联。这些方法是基于对象的锁机制实现的,用于线程间的协调和通信
join()
方法用于等待调用线程完成其执行,然后再继续执行当前线程
join()
方法会使当前线程进入阻塞状态,直到被调用线程执行完毕join()
方法中指定了超时时间,当前线程最多会等待指定的时间,然后继续执行join()
方法通常与多线程的任务分割和结果合并中使用wait()
、notify()
和notifyAll()
这三个方法是用于线程间的等待和唤醒机制,需要在同步代码块或同步方法中使用。被唤醒的线程会重新竞争对象的锁,一旦获得锁,就可以继续执行
wait()
方法使当前线程进入等待状态,放弃对象的锁,并等待其他线程调用相同对象的notify()
或notifyAll()
方法来唤醒它notify()
方法唤醒在相同对象上调用wait()
方法并进入等待状态的单个线程。notifyAll()
方法唤醒在相同对象上调用wait()
方法并进入等待状态的所有线程
wait()
方法在调用前需要先获得锁,否则会抛出IllegalMonitorStateException
异常
public class ThreadCooperationExample {
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("Thread 1 starts");
Thread.sleep(2000);
System.out.println("Thread 1 notifies");
lock.notify(); // 唤醒等待的线程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("Thread 2 starts");
lock.wait(); // 等待被唤醒
System.out.println("Thread 2 continues");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread1.start();
thread2.start();
thread1.join(); // 等待 thread1 执行完毕
thread2.join(); // 等待 thread2 执行完毕
System.out.println("Main thread finishes");
}
}
在上述示例中创建了两个线程 thread1 和 thread2。thread1 在执行过程中调用lock.notify()
方法唤醒等待的线程,而 thread2 在执行过程中调用lock.wait()
方法等待被唤醒。main 线程使用join()
方法等待 thread1 和 thread2 执行完毕后再继续执行
参考资料: