Java --- 多线程
目录
前言:
一.线程的创建:
1.通过继承 Thread 类来创建线程:
2.通过Runnable接口创建线程:
3.通过Java8引入的lambda语法:
线程的优先级:
二.线程的生命周期:
三. 中断线程:
线程中断的关键方法:
为什么用 while 而不用 if 判断?
四.守护线程:
五.线程的同步:
竞争条件:
1.使用 synchronized 关键字:
(1)同步实例方法:
(2)同步静态方法:
(3)同步代码块:
2.使用 volatile 关键字:
3.线程的等待(wait)与唤醒(notify):
等待-唤醒机制基本概念:
关键方法说明
4.使用 ReentrantLock锁:
对于ReentrantLock锁的等待与唤醒:
Condition 的常用方法:
六.线程池的使用:
1.线程池的核心接口和实现:
2. 线程池的常见实现类:
3.创建线程池:
(1)使用 Executors 工厂类创建线程池:
(2)使用 ThreadPoolExecutor 直接创建线程池:
4.线程池的常用方法:
(1)submit():
(2)shutdown() 和 shutdownNow():
(3)invokeAll() 和 invokeAny():
七.ThreadLocal的使用:
1.什么是ThreadLocal:
2.ThreadLocal的工作原理:
3.ThreadLocal的常见方法:
4.应用场景:
八.虚拟线程的使用:
1.什么是虚拟线程:
2.创建虚拟线程:
3.通过虚拟线程池管理虚拟线程:
4.虚拟线程的 I/O 操作:
Java多线程编程是并发编程的一部分,旨在通过并行执行任务提高程序的执行效率。Java提供了强大的多线程支持,包括 Thread
类和 Runnable
接口以及更高级的 Executor
服务、同步工具(如 synchronized
、Lock
)、条件变量等。
前言:
在计算机中,我们把一个任务称为一个进程,浏览器就是一个进程,视频播放器是另一个进程,类似的,音乐播放器和Word都是进程。
某些进程内部还需要同时执行多个子任务。例如,我们在使用Word时,Word可以让我们一边打字,一边进行拼写检查,同时还可以在后台进行打印,我们把子任务称为线程。
进程和线程的关系就是:一个进程可以包含一个或多个线程,但至少会有一个线程。
操作系统调度的最小任务单位其实不是进程,而是线程。常用的Windows、Linux等操作系统都采用抢占式多任务,如何调度线程完全由操作系统决定,程序自己不能决定什么时候执行,以及执行多长时间。
Java语言内置了多线程支持:一个Java程序实际上是一个JVM进程,JVM进程用一个主线程来执行main()
方法,在main()
方法内部,我们又可以启动多个线程。此外,JVM还有负责垃圾回收的其他工作线程等。
因此,对于大多数Java程序来说,我们说多任务,实际上是说如何使用多线程实现多任务。
和单线程相比,多线程编程的特点在于:多线程经常需要读写共享数据,并且需要同步。例如,播放电影时,就必须由一个线程播放视频,另一个线程播放音频,两个线程需要协调运行,否则画面和声音就不同步。因此,多线程编程的复杂度高,调试更困难。
Java多线程编程的特点又在于:
- 多线程模型是Java程序最基本的并发模型;
- 后续读写网络、数据库、Web开发等都依赖Java多线程模型。
因此,必须掌握Java多线程编程才能继续深入学习其他内容。
一.线程的创建:
1.通过继承 Thread 类来创建线程:
Java中可以通过继承 Thread
类来创建线程。在 Thread
类中重写 run()
方法,实现线程的执行逻辑。
// 继承 Thread 类创建线程
class MyThread extends Thread {@Overridepublic void run() {// 线程执行的任务System.out.println("线程 " + Thread.currentThread().getName() + " 正在执行");}
}public class ThreadDemo {public static void main(String[] args) {// 创建线程对象MyThread thread1 = new MyThread();MyThread thread2 = new MyThread();// 启动线程thread1.start();thread2.start();}
}
Thread
类提供了一个run()
方法,继承Thread
类并重写run()
方法,可以定义线程要执行的任务。start()
方法会启动线程并调用run()
方法。start()
方法会把线程放入线程调度队列,等待CPU分配时间片。
2.通过Runnable接口创建线程:
Runnable
接口是Java提供的一个函数式接口,定义了一个 run()
方法。线程可以通过实现 Runnable
接口来创建。
// 实现 Runnable 接口创建线程
class MyRunnable implements Runnable {@Overridepublic void run() {// 线程执行的任务System.out.println("线程 " + Thread.currentThread().getName() + " 正在执行");}
}public class RunnableDemo {public static void main(String[] args) {// 创建 Runnable 对象MyRunnable runnable = new MyRunnable();// 创建 Thread 对象,并传入 RunnableThread thread1 = new Thread(runnable);Thread thread2 = new Thread(runnable);// 启动线程thread1.start();thread2.start();}
}
Runnable
接口只有一个run()
方法,因此它比继承Thread
类更具灵活性,可以让一个任务实现多个线程。Thread
的构造方法可以传入一个实现了Runnable
接口的对象,通过start()
启动线程。
3.通过Java8引入的lambda语法:
// 多线程
public class ThreadDemo1 {public static void main(String[] args) {Thread t = new Thread(() -> {System.out.println("start new thread!");});t.start(); // 启动新线程}
}
线程的优先级:
我们可以对线程设定优先级,设定优先级的方法是:
Thread.setPriority(int n) // 1~10, 默认值5
JVM自动把1(低)~10(高)的优先级映射到操作系统实际优先级上(不同操作系统有不同的优先级数量)。优先级高的线程被操作系统调度的优先级较高,操作系统对高优先级线程可能调度更频繁,但我们决不能通过设置优先级来确保高优先级的线程一定会先执行。
二.线程的生命周期:
在Java程序中,一个线程对象只能调用一次 start()
方法启动新线程,并在新线程中执行 run()
方法。一旦 run()
方法执行完毕,线程就结束了。
因此Java中的线程有多个状态,常见的状态有:
- New:线程被创建但尚未启动。
- Runnable:线程在就绪队列中,等待 CPU 调度。
- Blocked:线程正在等待获取锁,进入阻塞状态。
- Waiting:线程调用
wait()
、join()
等方法进入等待状态,直到其他线程通知。- Timed Waiting:线程在指定时间内处于等待状态,超时后会被唤醒。
- Terminated:线程执行结束或由于异常终止。
当线程启动后,它可以在Runnable
、Blocked
、Waiting
和Timed Waiting
这几个状态之间切换,直到最后变成Terminated
状态,线程终止。
线程终止的原因有:
- 线程正常终止:
run()
方法执行到return
语句返回;- 线程意外终止:
run()
方法因为未捕获的异常导致线程终止;- 对某个线程的
Thread
实例调用stop()
方法强制终止(强烈不推荐使用)。
三. 中断线程:
线程中断是指通知一个线程它应该停止当前的工作并尽量提前退出。线程中断并不意味着线程会立即停止执行,而是线程响应中断信号并在适当的地方进行中断处理。Java中提供了对线程中断的支持。
Java中的线程中断是通过 Thread
类的 interrupt()
方法实现的。当一个线程被中断时,线程的中断状态会被设置为 true
,线程可以在自己合适的时机检查该中断标志并进行适当的处理。
线程中断的关键方法:
Thread.interrupt()
:用于请求中断线程。如果线程正在执行阻塞操作(如sleep()
、wait()
或join()
等),中断将抛出InterruptedException
异常;否则只是设置线程的中断标志位为true
。
Thread.isInterrupted()
:用于检查当前线程是否被中断。
Thread.interrupted()
:用于检查当前线程是否被中断并清除中断标志位。
class Task extends Thread {@Overridepublic void run() {while (!isInterrupted()) { // 检查线程的中断标志try {// 模拟长时间计算任务System.out.println("任务正在执行...");Thread.sleep(1000); // 可能被中断} catch (InterruptedException e) {// 如果发生中断,捕获异常并退出System.out.println("任务被中断,退出.");break;}}}
}public class InterruptDemo {public static void main(String[] args) throws InterruptedException {Task task = new Task();task.start();// 主线程休眠3秒后中断子线程Thread.sleep(3000);task.interrupt(); // 请求中断}
}
为什么用 while 而不用 if 判断?
中断的状态并不是一次性的,它可以被多次设置。Java 中的
Thread.interrupt()
方法只是设置线程的中断标志为true
,并不会强制终止线程。线程会根据自己的逻辑判断是否响应中断。使用while
循环可以确保即使线程在处理过程中发生了阻塞或某些逻辑判断,依然能够持续检查中断标志,及时响应外部的中断请求。
四.守护线程:
守护线程是指为其他线程提供服务的线程,在所有非守护线程结束时,守护线程也会自动终止。守护线程通常用于执行一些后台任务,如垃圾回收器或其他后台服务。
守护线程与非守护线程(用户线程)的最大区别在于,守护线程在没有任何非守护线程存活时自动退出,即使它们自己还在运行。换句话说,守护线程的生命周期是由非守护线程决定的,当所有非守护线程结束时,守护线程会被强制结束。
默认情况下,所有线程都是非守护线程(用户线程)。
我们可以通过
Thread.setDaemon(true)
来将线程设置为守护线程。
class DaemonTask extends Thread {@Overridepublic void run() {while (true) {System.out.println("守护线程正在运行...");try {Thread.sleep(1000); // 模拟执行任务} catch (InterruptedException e) {break;}}}
}public class DaemonThreadDemo {public static void main(String[] args) throws InterruptedException {DaemonTask daemonTask = new DaemonTask();daemonTask.setDaemon(true); // 设置为守护线程daemonTask.start();// 主线程休眠3秒后结束Thread.sleep(3000);System.out.println("主线程结束,守护线程也会自动退出.");}
}
五.线程的同步:
线程同步(Thread Synchronization)是多线程编程中的一个重要概念,指的是多个线程并发访问共享资源时,为了避免出现竞争条件(Race Condition)或不一致的数据状态,必须确保线程间的执行顺序和对共享资源的访问是有序的。线程同步通常用来保证多个线程对共享数据的访问是互斥的,即在同一时刻,只有一个线程可以访问共享资源。
竞争条件:
竞争条件是指多个线程并发执行时,如果没有正确的同步机制来保证线程间的顺序,可能导致不同线程对共享资源的访问互相干扰,从而造成数据不一致。
例如,下面的代码片段是一个简单的递增计数器,但由于多个线程并发操作,可能会导致计数器的值错误。
class Counter {private int count = 0;public void increment() {count++; // 非原子操作,可能导致数据不一致}public int getCount() {return count;}
}public class RaceConditionExample {public static void main(String[] args) throws InterruptedException {Counter counter = new Counter();Thread t1 = new Thread(() -> {for (int i = 0; i < 1000; i++) {counter.increment();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 1000; i++) {counter.increment();}});t1.start();t2.start();t1.join();t2.join();System.out.println("Final count: " + counter.getCount()); // 期望是 2000,但可能出现不一致}
}
在这个例子中,由于 count++
操作并非原子操作,多个线程同时执行时,可能会发生竞争条件,导致最终的计数值不为 2000。
为了防止竞争条件的发生,我们需要对共享资源的访问进行同步。同步能够确保同一时刻只有一个线程能够访问共享资源,从而避免多个线程的操作互相干扰。
在 Java 提供了多种方式来实现线程同步。常见的同步方法有 synchronized
关键字、显式锁(如 ReentrantLock
)、volatile
关键字等。
1.使用 synchronized
关键字:
synchronized
关键字是 Java 提供的最基本的同步机制,可以用来保证同一时刻只有一个线程能够访问某个代码块或方法。
synchronized
关键字有三种使用方式:
同步实例方法:将
synchronized
关键字加在实例方法上,这样每次只有一个线程能够执行该实例方法。同步静态方法:将
synchronized
关键字加在静态方法上,这样每次只有一个线程能够执行该静态方法。同步代码块:将
synchronized
关键字用于方法内部的代码块,只对特定代码块进行同步,减少性能开销。
(1)同步实例方法:
class Counter {private int count = 0;// 使用 synchronized 确保每次只有一个线程可以执行该方法public synchronized void increment() {count++;}public synchronized int getCount() {return count;}
}public class SynchronizedExample {public static void main(String[] args) throws InterruptedException {Counter counter = new Counter();Thread t1 = new Thread(() -> {for (int i = 0; i < 1000; i++) {counter.increment();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 1000; i++) {counter.increment();}});t1.start();t2.start();t1.join();t2.join();System.out.println("Final count: " + counter.getCount()); // 正常输出 2000}
}
synchronized
确保了在同一时刻只有一个线程能够执行increment()
方法。- 通过同步方法,
count++
操作不会被多个线程同时执行,避免了数据不一致的情况。
(2)同步静态方法:
synchronized static
确保了在同一时刻,只有一个线程能够执行increment()
静态方法。
class Counter {private static int count = 0;// 使用 synchronized 确保每次只有一个线程可以执行该静态方法public synchronized static void increment() {count++;}public synchronized static int getCount() {return count;}
}public class SynchronizedStaticExample {public static void main(String[] args) throws InterruptedException {Counter counter = new Counter();Thread t1 = new Thread(() -> {for (int i = 0; i < 1000; i++) {counter.increment();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 1000; i++) {counter.increment();}});t1.start();t2.start();t1.join();t2.join();System.out.println("Final count: " + counter.getCount()); // 正常输出 2000}
}
(3)同步代码块:
synchronized (this)
确保只有一个线程可以执行increment()
方法中的关键部分。通过同步代码块,我们可以更精细地控制同步范围,减少性能开销。
class Counter {private int count = 0;public void increment() {synchronized (this) { // 只对特定代码块进行同步count++;}}public int getCount() {return count;}
}public class SynchronizedBlockExample {public static void main(String[] args) throws InterruptedException {Counter counter = new Counter();Thread t1 = new Thread(() -> {for (int i = 0; i < 1000; i++) {counter.increment();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 1000; i++) {counter.increment();}});t1.start();t2.start();t1.join();t2.join();System.out.println("Final count: " + counter.getCount()); // 正常输出 2000}
}
2.使用 volatile
关键字:
volatile
关键字保证了变量的可见性,即线程修改了 volatile
变量的值,其他线程能够立即看到修改后的值。volatile
并不保证原子性,但它可以用于避免一些简单的同步问题,如标志变量的检测。
使用
volatile
保证了flag
变量的值在不同线程间的可见性。
class SharedResource {private volatile boolean flag = false;public void setFlagTrue() {flag = true; // 设置 flag 为 true}public boolean getFlag() {return flag; // 读取 flag 的值}
}
3.线程的等待(wait)与唤醒(notify):
除了常见的线程互斥功能外,synchronized
还提供了一种 等待-唤醒机制,允许线程在特定条件下挂起,并等待其他线程的通知。
这种机制通常用于实现线程间的通信,如 生产者-消费者问题,即线程A在资源不足时进入等待状态,而线程B则在资源有了后通知线程A继续执行。
等待-唤醒机制基本概念:
- 等待(
wait()
):当线程执行到某一条件不满足时,使用wait()
让该线程进入等待状态,释放持有的锁,直到其他线程通过调用notify()
或notifyAll()
唤醒它。- 唤醒(
notify()
或notifyAll()
):当某些条件改变时,其他线程调用notify()
或notifyAll()
唤醒处于等待状态的线程。notify()
唤醒一个线程,notifyAll()
唤醒所有等待的线程
关键方法说明
wait()
:使当前线程进入等待状态,释放持有的锁,直到其他线程调用notify()
或notifyAll()
唤醒它。wait()
必须在同步块或同步方法中调用。使当前线程等待,并且释放持有的锁。
notify()
:唤醒一个等待的线程。如果多个线程在等待该对象的监视器(锁),notify()
会唤醒其中一个线程(具体哪个线程无法确定)。
notifyAll()
:唤醒所有等待的线程,所有等待该对象监视器的线程都会被唤醒。
wait()
和notify()
只能在同步块或同步方法中调用,因为它们依赖于对象的监视器(锁)。在同步方法内调用时,该方法的锁是当前对象;在同步代码块内,锁是传入的对象。在调用
wait()
时,线程会释放当前的锁,允许其他线程获取锁。当该线程被唤醒后,它会重新尝试获取锁。
class BoundedBuffer {private int[] buffer;private int count = 0; // 缓冲区中的元素个数private int putIndex = 0; // 下一次插入的位置private int takeIndex = 0; // 下一次取出的位置// 创建缓冲区public BoundedBuffer(int size) {buffer = new int[size];}// 生产者放入数据public synchronized void put(int value) throws InterruptedException {while (count == buffer.length) { // 如果缓冲区满wait(); // 生产者等待}buffer[putIndex] = value;putIndex = (putIndex + 1) % buffer.length; // 循环插入count++;notifyAll(); // 通知消费者可以取数据了}// 消费者取出数据public synchronized int take() throws InterruptedException {while (count == 0) { // 如果缓冲区空wait(); // 消费者等待}int value = buffer[takeIndex];takeIndex = (takeIndex + 1) % buffer.length; // 循环取出count--;notifyAll(); // 通知生产者可以放数据了return value;}
}public class ProducerConsumer {public static void main(String[] args) throws InterruptedException {BoundedBuffer buffer = new BoundedBuffer(10);// 生产者线程Thread producer = new Thread(() -> {try {for (int i = 0; i < 100; i++) {buffer.put(i);System.out.println("Produced: " + i);}} catch (InterruptedException e) {Thread.currentThread().interrupt();}});// 消费者线程Thread consumer = new Thread(() -> {try {for (int i = 0; i < 100; i++) {int value = buffer.take();System.out.println("Consumed: " + value);}} catch (InterruptedException e) {Thread.currentThread().interrupt();}});producer.start();consumer.start();producer.join();consumer.join();}
}
4.使用 ReentrantLock锁:
在 Java 中,多线程同步的传统方法是使用 synchronized
关键字。虽然 synchronized
简单易用,但它存在一些不足之处,如无法精确控制锁的获取和释放、缺少条件等待和通知机制等。为了解决这些问题,Java 提供了显式锁机制 ReentrantLock
(可重入锁)和 Condition
类,能够提供更多的灵活性和控制。
ReentrantLock
是 java.util.concurrent.locks
包下的一个类,它是一个可重入的互斥锁,允许在一个线程中多次获取同一个锁,并且能够提供比 synchronized
更灵活的功能。
ReentrantLock
提供了对count++
操作的显式同步,保证了并发情况下count
的值是正确的。lock.lock()
获取锁,lock.unlock()
释放锁。如果忘记调用unlock()
,可能会导致死锁问题,因此需要确保在finally
块中释放锁。
import java.util.concurrent.locks.ReentrantLock;class Counter {private int count = 0;private final ReentrantLock lock = new ReentrantLock();// 使用 ReentrantLock 进行线程同步public void increment() {lock.lock(); // 获取锁try {count++;} finally {lock.unlock(); // 释放锁}}public int getCount() {return count;}
}public class ReentrantLockExample {public static void main(String[] args) throws InterruptedException {Counter counter = new Counter();Thread t1 = new Thread(() -> {for (int i = 0; i < 1000; i++) {counter.increment();}});Thread t2 = new Thread(() -> {for (int i = 0; i < 1000; i++) {counter.increment();}});t1.start();t2.start();t1.join();t2.join();System.out.println("Final count: " + counter.getCount()); // 正常输出 2000}
}
对于ReentrantLock锁的等待与唤醒:
Condition
是与 ReentrantLock
配合使用的一个接口,它允许线程在满足某个条件时进行等待或通知,类似于传统的 Object.wait()
和 Object.notify()
方法。Condition
提供了更多的控制选项,可以精确控制等待和通知的条件。
Condition 的常用方法:
await()
:使当前线程等待,直到被其他线程通知或者中断。
signal()
:唤醒一个等待的线程,使其能够继续执行。
signalAll()
:唤醒所有等待的线程,使它们能够继续执行。
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;class BoundedBuffer {private final int[] buffer; // 缓冲区private int count = 0;private int putIndex = 0;private int takeIndex = 0;private final ReentrantLock lock = new ReentrantLock();private final Condition notFull = lock.newCondition(); // 缓冲区不满时的条件private final Condition notEmpty = lock.newCondition(); // 缓冲区不空时的条件public BoundedBuffer(int size) {buffer = new int[size];}public void put(int value) throws InterruptedException {lock.lock();try {while (count == buffer.length) {notFull.await(); // 如果缓冲区满,等待}buffer[putIndex] = value;if (++putIndex == buffer.length) {putIndex = 0; // 环形缓冲区}count++;notEmpty.signal(); // 唤醒等待取数据的线程} finally {lock.unlock();}}public int take() throws InterruptedException {lock.lock();try {while (count == 0) {notEmpty.await(); // 如果缓冲区为空,等待}int value = buffer[takeIndex];if (++takeIndex == buffer.length) {takeIndex = 0; // 环形缓冲区}count--;notFull.signal(); // 唤醒等待放数据的线程return value;} finally {lock.unlock();}}
}public class ProducerConsumerExample {public static void main(String[] args) throws InterruptedException {BoundedBuffer buffer = new BoundedBuffer(10);// 生产者线程Thread producer = new Thread(() -> {try {for (int i = 0; i < 100; i++) {buffer.put(i);System.out.println("Produced: " + i);}} catch (InterruptedException e) {Thread.currentThread().interrupt();}});// 消费者线程Thread consumer = new Thread(() -> {try {for (int i = 0; i < 100; i++) {int value = buffer.take();System.out.println("Consumed: " + value);}} catch (InterruptedException e) {Thread.currentThread().interrupt();}});producer.start();consumer.start();producer.join();consumer.join();}
}
六.线程池的使用:
在多线程编程中,创建和销毁线程是一个非常耗费资源的操作。如果频繁地创建和销毁线程,会导致性能下降,甚至可能引发系统崩溃。因此,Java 提供了 线程池 的机制来管理线程的生命周期,避免每次任务执行时都需要创建新线程。
线程池是一种常用的设计模式,可以复用已创建的线程,而不是每次都创建新线程。Java 提供了 Executor
框架来管理线程池及其任务。
1.线程池的核心接口和实现:
Executor:是线程池的核心接口,提供了执行任务的方法。
public interface Executor {void execute(Runnable command);
}
ExecutorService:是 Executor
的子接口,提供了更多的功能,比如管理线程池、提交任务、关闭线程池等。
public interface ExecutorService extends Executor {void shutdown();List<Runnable> shutdownNow();<T> Future<T> submit(Callable<T> task);<T> Future<T> submit(Runnable task, T result);
}
ThreadPoolExecutor:是 ExecutorService
的常用实现类,提供了线程池的管理功能。它是一个可定制化的线程池实现。
ScheduledExecutorService:继承了 ExecutorService
,用于执行定时任务。
2. 线程池的常见实现类:
FixedThreadPool:创建一个固定大小的线程池。线程池中的线程数量固定,任务会在空闲的线程上执行。适用于任务量较大、每个任务执行时间相似的场景。
CachedThreadPool:创建一个可缓存的线程池。如果线程池中的线程空闲超过60秒,则会被回收。适用于任务量不确定,且每个任务的执行时间较短的场景。
SingleThreadExecutor:创建一个只有一个线程的线程池,适用于任务顺序执行的场景。
ScheduledThreadPoolExecutor:用于执行定时任务,支持延时执行任务、周期性任务等
3.创建线程池:
(1)使用 Executors
工厂类创建线程池:
Java 提供了 Executors
类来创建各种类型的线程池。以下是一些常见的线程池创建方式:
import java.util.concurrent.*;public class ThreadPoolExample {public static void main(String[] args) {// 固定大小线程池ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);// 可缓存线程池ExecutorService cachedThreadPool = Executors.newCachedThreadPool();// 单线程池ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();// 定时线程池ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2);}
}
(2)使用 ThreadPoolExecutor
直接创建线程池:
ThreadPoolExecutor
提供了更灵活的创建方式,可以定制线程池的各个参数。常用的构造方法如下:
ThreadPoolExecutor(int corePoolSize, // 核心池大小(即使没有任务时,线程池也会保留的最小线程数)int maximumPoolSize, // 最大池大小(线程池能够容纳的最大线程数)long keepAliveTime, // 空闲线程存活时间(当线程池中的线程数大于 corePoolSize 时,空闲线程的最大存活时间)TimeUnit unit, // 时间单位BlockingQueue<Runnable> workQueue // 用于保存任务的阻塞队列// LinkedBlockingQueue:无界队列// ArrayBlockingQueue:有界队列// PriorityBlockingQueue:优先级队列
)
4.线程池的常用方法:
(1)submit():
submit()
方法可以提交一个 Runnable
或 Callable
任务,返回一个 Future
对象,允许在任务完成后获取其结果。
ExecutorService executor = Executors.newFixedThreadPool(3);
Future<Integer> future = executor.submit(() -> {// 任务逻辑return 123;
});
Integer result = future.get(); // 获取任务执行结果
System.out.println(result);
(2)shutdown() 和 shutdownNow():
executor.shutdown(); // 启动关闭过程,等待所有任务完成
executor.shutdownNow(); // 尝试停止正在执行的任务
(3)invokeAll() 和 invokeAny():
- invokeAll():一次执行多个任务,并返回所有任务的
Future
列表,等待所有任务完成。- invokeAny():一次执行多个任务,返回最先完成任务的结果。
List<Callable<Integer>> tasks = new ArrayList<>();
tasks.add(() -> 1);
tasks.add(() -> 2);ExecutorService executor = Executors.newFixedThreadPool(3);
List<Future<Integer>> futures = executor.invokeAll(tasks);
for (Future<Integer> future : futures) {System.out.println(future.get());
}Integer result = executor.invokeAny(tasks); // 返回第一个完成的任务结果
System.out.println(result);
七.ThreadLocal的使用:
ThreadLocal
是 Java 提供的一种用于解决多线程并发问题的机制。它能够为每个线程提供独立的变量副本,这样多个线程访问同一个 ThreadLocal
变量时,各自的副本不会相互影响,避免了线程安全问题。
在多线程环境下,通常多个线程会共享某些数据,但如果多个线程同时访问同一份数据,可能会引发数据竞争和线程安全问题。ThreadLocal
通过为每个线程提供独立的副本来解决这一问题。
1.什么是ThreadLocal:
ThreadLocal
提供了一个每个线程私有的变量副本。每个线程在首次访问 ThreadLocal
时,会为该线程创建一个 ThreadLocal
变量的副本。之后同一线程访问该变量时,将直接操作该线程的副本,而不会与其他线程的副本发生冲突。
每个线程可以通过
ThreadLocal
变量独立地获取、设置自己的值。
2.ThreadLocal的工作原理:
ThreadLocal
工作的核心是 线程隔离。每个线程都有自己独立的 ThreadLocal
变量副本,访问这个副本的操作不会影响其他线程的副本。每个线程都有一个与 ThreadLocal
对象关联的 线程本地存储。
具体而言:
- 当线程第一次访问
ThreadLocal
变量时,系统会创建该线程本地的副本并初始化它。- 每个线程可以通过
ThreadLocal
类提供的get()
和set()
方法来获取和设置它的本地副本。- 不同线程间互不干扰,
ThreadLocal
实现了线程数据隔离。
3.ThreadLocal的常见方法:
(1)get():获取当前线程对 ThreadLocal
变量的副本。
public T get();
(2)set(T value):设置当前线程对 ThreadLocal
变量的副本。
public void set(T value);
(3)initialValue():返回当前线程对 ThreadLocal
变量的初始值。如果没有设置初始值,会调用此方法生成。
protected T initialValue();
4.应用场景:
在下面的例子中:
threadLocal
是一个ThreadLocal<Integer>
变量,它为每个线程维护了一个独立的副本。- 通过
threadLocal.get()
和threadLocal.set()
,每个线程操作的是自己私有的副本,互不干扰。- 线程1和线程2的初始值都是
1
,但是它们在各自的上下文中可以独立地修改这个值。
public class ThreadLocalExample {private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 1);public static void main(String[] args) {// 启动多个线程来演示 ThreadLocal 的使用Thread thread1 = new Thread(() -> {System.out.println("Thread 1 initial value: " + threadLocal.get()); // 1threadLocal.set(2);System.out.println("Thread 1 new value: " + threadLocal.get()); // 2});Thread thread2 = new Thread(() -> {System.out.println("Thread 2 initial value: " + threadLocal.get()); // 1threadLocal.set(3);System.out.println("Thread 2 new value: " + threadLocal.get()); // 3});thread1.start();thread2.start();}
}
ThreadLocal
的withInitial
方法允许你指定一个初始值。如果线程没有显式设置值,ThreadLocal
会使用该初始值。ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "Default Value");public class ThreadLocalExample {public static void main(String[] args) {System.out.println(threadLocal.get()); // 输出: Default Value} }
八.虚拟线程的使用:
1.什么是虚拟线程:
虚拟线程是 JEP 436 中提出的一项新特性,旨在简化 Java 并发编程并提高应用程序的可扩展性。虚拟线程是基于 轻量级线程(Lightweight Threads)设计的,它们由 Java 虚拟机(JVM)进行调度,而不是由操作系统进行调度。虚拟线程的引入将有助于大规模并发编程,特别是在需要大量并发线程的应用场景中(如 Web 服务器、异步 I/O 等)。
虚拟线程的目标是提供与传统的操作系统线程(称为“平台线程”)相同的编程模型,但它们在系统资源的使用上更加高效,从而显著提高程序的并发性能。
2.创建虚拟线程:
虚拟线程可以通过 Thread.ofVirtual()
方法创建,或者通过 ExecutorService
来管理虚拟线程池。
public class VirtualThreadExample {public static void main(String[] args) {Thread virtualThread = Thread.ofVirtual().start(() -> {System.out.println("Virtual thread is running!");});// 等待线程执行完毕try {virtualThread.join();} catch (InterruptedException e) {e.printStackTrace();}}
}
3.通过虚拟线程池管理虚拟线程:
我们可以使用 ExecutorService
来管理虚拟线程池,这样可以简化多线程管理。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class VirtualThreadExecutorExample {public static void main(String[] args) {// 创建一个虚拟线程池ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();// 提交任务到虚拟线程池for (int i = 0; i < 10; i++) {final int taskId = i;executor.submit(() -> {System.out.println("Task " + taskId + " is running on virtual thread.");});}// 关闭线程池executor.shutdown();}
}
4.虚拟线程的 I/O 操作:
虚拟线程特别适合 I/O 密集型操作。使用虚拟线程时,JVM 会自动挂起不活跃的线程并将 CPU 资源分配给其他线程,从而避免了传统线程模型中因为 I/O 操作导致线程空闲的资源浪费。
import java.util.concurrent.*;public class IOBlockingExample {public static void main(String[] args) {ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();for (int i = 0; i < 10; i++) {final int taskId = i;executor.submit(() -> {try {System.out.println("Task " + taskId + " starts I/O operation.");// 模拟 I/O 操作Thread.sleep(1000);System.out.println("Task " + taskId + " finishes I/O operation.");} catch (InterruptedException e) {Thread.currentThread().interrupt();}});}executor.shutdown();}
}
相关文章:
Java --- 多线程
目录 前言: 一.线程的创建: 1.通过继承 Thread 类来创建线程: 2.通过Runnable接口创建线程: 3.通过Java8引入的lambda语法: 线程的优先级: 二.线程的生命周期: 三. 中断线程:…...
医学图像 三维重建,原图与灰度图叠加,原图与多图叠加显示;多图像融合显示,彩色灰度图像融合
Part1: Summary 我们在做图像分割或融合时,有时需要显示多份数据进行叠加显示;可能需要这种效果: 四视图: 基于这个,我看一下网上的实现总结了一下;实现了以下几种效果: Part2:多种…...
Linux中的多线程
1.Linux线程概念 什么叫做线程? 我们认为,线程操作系统调度的基本单位!重新理解进程? Linux内核观点:进程是承担分配系统资源的基本实体,即操作系统分配资源,是以进程为单位进行分配的。线程是进程内部的执行流资源…...
hive常用函数有哪些
Hive是一个基于Hadoop的数据仓库工具,它提供了类似于SQL的接口,用于数据查询和分析。Hive提供了许多内置函数,这些函数可以分为几种类型,包括: • 字符串函数:用于处理字符串数据。 • concat()࿱…...
深度学习试题及答案解析(二)
1. 神经风格转换中,优化算法的每次迭代更新的是什么? 神经风格转换(Neural Style Transfer, NST)是一种使用深度学习技术,特别是卷积神经网络(CNN),来将一幅图像的风格应用到另一幅图…...
【CSS in Depth 2 精译_089】15.2:CSS 过渡特效中的定时函数
当前内容所在位置(可进入专栏查看其他译好的章节内容) 第五部分 添加动效 ✔️【第 15 章 过渡】 ✔️ 15.1 状态间的由此及彼15.2 定时函数 ✔️ 15.2.1 定制贝塞尔曲线 ✔️15.2.2 阶跃 ✔️ 15.3 非动画属性 文章目录 15.2 定时函数 Timing function…...
LINUX内核常用加锁
1、mutex互斥锁 互斥锁的实现主要利用到了原子变量可以锁内存总线的机制来对lock变量值进行原子修改,并通过在加锁及释放锁过程中引入内存屏障(加锁引入lfence,释放锁引入sfence),来确保锁临界区资源(Critical Section)能够在不同的CPU之间可…...
【Select 语法全解密】.NET开源ORM框架 SqlSugar 系列
系列文章目录 🎀🎀🎀 .NET开源 ORM 框架 SqlSugar 系列 🎀🎀🎀 文章目录 系列文章目录前言一、Select 执行位置二、返回一个字段和多个字段三、单表返回DTO四、多表返回DTO4.1 手动DTO4.2 实体自动映射14.…...
STM32之GPIO输出与输出
欢迎来到 破晓的历程的 博客 ⛺️不负时光,不负己✈️ 文章目录 一.GPIO输入1.1GPIP简介1.2GPIO基本结构1.3GPIO位结构1.4GPIO的八种模式1.4.1浮空/上拉/下拉输入1.4.2 模拟输入1.4.3 推挽输出\开漏输出 二.GPIO输入2.1.按键介绍2.2传感器模块介绍2.3按键电路 一.G…...
【数据库】Redis—Java 客户端
一、常见的几种 Java 客户端 Jedis:以 Redis 命令作为方法的名称,便于学习,简单实用,但其实例是线程不安全的,多线程下需要基于连接池来使用。lettce:基于 Netty 实现,支持同步、异步和响应式编…...
《图解机器学习》(杉山将著)第一部分绪论学习笔记
《图解机器学习》(杉山将著)第一部分绪论学习笔记 《图解机器学习》(杉山将著)第一部分绪论学习笔记一、什么是机器学习1.1 学习的种类1.2 机器学习任务的例子1.3 机器学习的方法 二、学习模型2.1 线性模型2.2 核模型2.3 层级模型…...
Deepin和Windows传文件(Xftp,WinSCP)
在Linux系统和Windows系统传输文件,通常通过Windows系统中安装的Xftp和WinSCP访问Linux系统,在访问前需要安装配置SSH-Server 安装SSH-Server 安装SSH-Server sudo apt-get install openssh-server ssh -v 启动SSH服务 sudo systemctl start ssh //也…...
C语言习题2.0
C语言习题1.0 C语言习题-CSDN博客 目录 C语言习题1.0 C语言习题-CSDN博客 找一个数字的连续因子 求N个分数的和 正整数AB 函数 预处理 文件处理 操作符 找一个数字的连续因子 //找连续因子,及其个数 int main() {int a;scanf("%d", &a);int num 0; …...
达梦 本地编码:PG_GBK, 导入文件编码:PG_UTF8错误
问题 达梦 本地编码:PG_GBK, 导入文件编码:PG_UTF8错误 解决 右键管理服务器 查看配置 新建一个数据库实例,配置跟之前的保持一致 新建一个用户,跟以前的用户名一样 在用户上,右键导入,选择dmp的位置 导…...
【Apache Paimon】-- 11 -- Flink 消费 kakfa 写 S3 File
目录 1、项目构建 2、项目新增和修改 2.1 pom.xml 新增依赖 2.2 本地测试或者 flink on k8s 时,新增 S3FileSystemFactory.java 第一步:创建包=org.apache.flink.fs.s3hadoop 第二步:新增 java 类 S3FileSystemFactory 特别注意 (1)本地测试时需要新增以下内容 (…...
使用C语言编写UDP循环接收并打印消息的程序
使用C语言编写UDP循环接收并打印消息的程序 前提条件程序概述伪代码C语言实现编译和运行C改进之自由设定端口注意事项在本文中,我们将展示如何使用C语言编写一个简单的UDP服务器程序,该程序将循环接收来自指定端口的UDP消息,并将接收到的消息打印到控制台。我们将使用POSIX套…...
QT6静态编译并配置及错误解决
使用Github workflow进行编译,无需本地编译。 断断续续半年间,试了很多次静态编译,也尝试过别人编译的静态包,但一直失败。不是无法成功编译,就是编译后无法正常使用,经常报错链接失败。 参考的教程&#…...
Docker部署GitLab服务器
一、GitLab介绍 1.1 GitLab简介 GitLab 是一款基于 Git 的开源代码托管平台,集成了版本控制、代码审查、问题跟踪、持续集成与持续交付(CI/CD)等多种功能,旨在为团队提供一站式的项目管理解决方案。借助 GitLab,开发…...
flink实现复杂kafka数据读取
接上文:一文说清flink从编码到部署上线 环境说明:MySQL:5.7;flink:1.14.0;hadoop:3.0.0;操作系统:CentOS 7.6;JDK:1.8.0_401。 常见的文章中&…...
小雅Alist缓存太多怎么清理?教程来了
声明:不喜欢小白在开头唠嗑的小伙伴可以直接滑动到【 正文开始】处阅读。 前言 前段时间讲到在飞牛OS上部署小雅超集AList,后台看到很多小伙伴都部署了。 飞牛NAS上的小雅根本没有资源?只剩下打赏码?那得按照这个重新配置了&…...
Python字符串及正则表达式(十一):正则表达式、使用re模块实现正则表达式操作
前言:在 Python 编程的广阔天地中,字符串处理无疑是一项基础而关键的技能。正则表达式,作为处理字符串的强大工具,以其灵活的模式匹配能力,在文本搜索、数据清洗、格式验证等领域发挥着不可替代的作用。本系列博客已经…...
前端:金额高精度处理
Decimal 是什么 想必大家在用js 处理 数字的 加减乘除的时候,或许都有遇到过 精度不够 的问题,还有那些经典的面试题 0.20.1 ! 0.3, 至于原因,那就是 js 计算底层用的是 IEEE 754 ,精度上有限制, 那么Deci…...
WPF 依赖属性和附加属性
除了普通的 CLR 属性, WPF 还有一套自己的属性系统。这个系统中的属性称为依赖属性。 1. 依赖属性 为啥叫依赖属性?不叫阿猫阿狗属性? 通常我们定义一个普通 CLR 属性,其实就是获取和设置一个私有字段的值。假设声明了 100 个 …...
git merge 冲突 解决 show case
废话不多说,上 case!!! 1. 更新master分支 package org.example;public class Main {public static void main(String[] args) {System.out.println("--------Git冲突测试代码开始---------");System.out.println(&qu…...
小鹏“飞行汽车”上海首飞,如何保障智能出行的安全性?
近日,小鹏汇天的“陆地航母”飞行汽车在上海陆家嘴成功完成首飞,标志着飞行汽车时代在中国正式拉开序幕。作为一项突破性的科技创新,飞行汽车不仅将重塑我们的出行方式,还将深刻改变城市的交通格局。此次飞行不仅证明了飞行汽车的…...
分析excel硕士序列数据提示词——包含对特征的筛选,非0值的过滤
文章目录 1 分析出发点2 围绕出发点的文件分析3 功能模块计算重心相关性计算教学倾向百分比多列相关性计算结果封装证伪——过滤0后的交叉相关系数封装和总控——批量处理特征筛选——筛选提问倾向最大和最小的前五代码总的清洗1 分析出发点 写一个python代码,遍历"D:\Ba…...
sed | 一些关于 sed 的笔记
sed 总结 sed 语法sed [-hnV] [-e<script>] [-f<script文件>] [文本文件]--- 参数:-e<script> 以选项中指定的script 来处理输入的文本文件-f<script文件> 以选项中指定的script 文件来处理输入的文本文件动作说明:a 新增s 取代d 删除i 插入…...
如何重新设置VSCode的密钥环密码?
故障现象: 忘记了Vscode的这个密码: Enter password to unlock An application wants access to the keyring “Default ke... Password: The unlock password was incorrect Cancel Unlock 解决办法: 1.任意terminal下,输入如下…...
数据结构 (字符串:KMP)
KMP算法: 构造ne数组 和 匹配过程 vector<int> build_next(string s) { vector<int> ne(s.size()); ne[0] -1; for (int i 1, j -1; i < s.size(); i) { while (j ! -1 && s[i] ! s[j 1])j ne[j]; if (s[i…...
剑指offer搜索二维矩阵
题目连接 https://leetcode.cn/problems/search-a-2d-matrix-ii/’ 代码 自己想出来的 解法一 初始化两个指针,i0,j列数-1 若此时matrix[i][j]target 则返回true 若此时matrix[i][j]>target,表明在第j列中不可能存在target,因为列是升序的 若此时ma…...
【LIBS】开源库编译之OSQP
目录 编译环境源码下载本地编译交叉编译 编译环境 【LIBS】开源库编译之编译环境 源码下载 git clone --recursive --branch v0.6.3 https://github.com/osqp/osqp.git libosqp-0.6.3本地编译 cd libosqp-0.6.3cmake -B build_amd64 \-D CMAKE_BUILD_TYPERelease \-D CMAKE…...
【系统移植】制作SD卡启动——将uboot烧写到SD卡
在开发板上启动Linux内核,一般有两种方法,一种是从EMMC启动,还有一种就是从SD卡启动,不断哪种启动方法,当开发板上电之后,首先运行的是uboot。 制作SD卡启动,首先要将uboot烧写到SD卡ÿ…...
.NET重点
B/S C/S什么语言 B/S: 浏览器端:JavaScript,HTML,CSS 服务器端:ASP(.NET)PHP/JSP 优势:维护方便,易于升级和扩展 劣势:服务器负担沉重 C/S java/.NET/…...
米思奇图形化编程之ESP32控制LED灯闪烁方案实现
目录 一、项目概述 二、硬件准备 三、硬件连接 四、软件编程 五、验证效果 六、总结 一、项目概述 本项目使用米思奇图形化编程环境,编写micropython软件代码,实现了控制ESP32开发板上LED灯闪烁效果。该项目可为后续更复杂的物联网项目打下基础。…...
SMMU软件指南SMMU编程之全局错误和最小配置
安全之安全(security)博客目录导读 目录 一、全局错误 二、最小配置 一、全局错误 与编程接口相关的全局错误会报告到适当的 SMMU_(*_)GERROR 寄存器,而不是通过基于内存的事件队列。这些错误通常是严重的,例如导致 SMMU 停止向前推进。例如…...
JavaEE 导读与环境配置
JavaEE 介绍 Java EE(Java Platform Enterprise Edition), Java 平台企业版. 是JavaSE的扩展, ⽤于解决企业级的开发需求, 所以也可以称之为是⼀组⽤于企业开发的Java技术标准. 所以, 学习JavaEE主要是学习Java在企业中如何应⽤ 框架学习 Java EE 课程共涉及4个框架的学习: Spr…...
Cesium 实例化潜入潜出
Cesium 实例化潜入潜出 1、WebGL Instance 的原理 狭义的的WebGL 中说使用 Instance, 一般指使用 glDrawArraysInstanced 用于实例化渲染的函数。它允许在一次绘制调用中渲染多个相同的几何体实例,而无需为每个实例发起单独的绘制调用。 Three.js 就是使用这种方…...
设计模式——适配器模式
1. 定义 适配器模式(Adapter Pattern)属于结构型设计模式,它的主要作用是将一个类的接口转换成客户期望的另一个接口,使得原本由于接口不兼容而不能一起工作的类可以协同工作。简单来说,就是充当了不同接口之间的 “转…...
Linux安装Docker
如何在虚拟机中安装linux操作系统,参考文章: VMware下Centos7安装步骤-CSDN博客 使用Xshell链接到linux操作系统 执行命令,安装docker的底层工具 sudo yum install -y yum-utils device-mapper-persistent-data lvm2 增加阿里云docker下载仓…...
CH430N 插上电脑无反应
电路图,此处我用的是3.3V供电,现象就是插上USB,电脑没有反应。搜索也搜索不到 抄板请看自己是多少V供电 后来看到也有类似的 换了芯片后就好了。md新板子第一个芯片就是坏的,服了。...
Java实现贪吃蛇游戏
目录 一、项目结构 二、实现步骤 1. 创建 Snake 类 2. 创建 Food 类 3. 创建 GameBoard 类 4. 创建 SnakeGame 类 三、总结 贪吃蛇是一个经典的电子游戏,它的玩法非常简单,但又充满了挑战。玩家通过控制蛇的移动,吃到食物并不断成长&a…...
为SSH2协议服务器的用户设置密钥
目录 私钥的创建1. 在服务器上直接生成2. 如果需要配置免密登录3. 查看生成的密钥 导出私钥至SSH用户获取sudo权限 新的一台服务器类型是SSH2:这表示服务器支持SSH(Secure Shell)协议的第二个版本。SSH是一个网络协议,用于加密方式…...
最新版Chrome浏览器加ActiveX控件之多个VLC控件同时加载
背景 VLC Media Player 是一款可播放大多数格式,而无需安装编解码器包的媒体播放器。可以播放 MPEG-1、MPEG-2、MPEG-4、DivX、DVD/VCD、卫星数字电视频道、地面数字电视频道(digital terrestrial television channels)、在许多作业平台底下透过宽带 IPv4、IPv6 网络…...
sql server 查询对象的修改时间
sql server 不能查询索引的最后修改时间,可以查询表,存储过程,函数,pk 的最后修改时间使用以下语句 select * from sys.all_objects ob order by ob.modify_date desc 但可以参考一下统计信息的最后修改时间,因为索…...
均值聚类算法
K-均值聚类算法是一种常用的无监督学习算法,用于将数据集划分成K个不同的簇。该算法的步骤如下: 1. 选择聚类的个数K。 2. 随机初始化K个聚类中心。 3. 对每个数据点计算其与聚类中心的距离,并将其划分到最近的聚类中心所代表的簇。 4. 对每…...
django中cookie与session的使用
一、cookie cookie由服务器生成 ,存储在浏览器中的键值对数据,具有不安全性,对应敏感数据应该加密储存在服务端每个域名的cookie相互独立浏览器访问域名为A的url地址,会把A域名下的cookie一起传递到服务器cookie可以设置过期时间 django中设…...
Python 协程:并发编程的轻量级利器
一、协程是什么? 协程是一种比线程更加轻量级的存在。它可以在特定的点暂停执行,然后在后续某个时刻恢复执行,并且能够在暂停期间保存其执行状态。与传统的多线程模型不同,协程不需要操作系统进行线程上下文切换的开销,…...
理解JVM
JVM简介 JVM 是 Java Virtual Machine 的简称,意为 Java 虚拟机 虚拟机是指通过软件模拟的具有完整硬件功能的、运行在一个完全隔离的环境中的完整计算机系统 常见的虚拟机: JVM 、 VMwave 、 Virtual Box JVM 和其他两个虚拟机的区别: 1…...
GhostRace: Exploiting and Mitigating Speculative Race Conditions-记录
文章目录 论文背景Spectre-PHT(Transient Execution )Concurrency BugsSRC/SCUAF和实验条件 流程Creating an Unbounded UAF WindowCrafting Speculative Race ConditionsExploiting Speculative Race Conditions poc修复flush and reload 论文 https:/…...
【环境搭建】使用IDEA远程调试Dolphinscheduler
以Dolphinscheduler-3.1.8为例,先把容器起了: $ docker pull apache/dolphinscheduler-standalone-server:3.1.8 $ docker run -d -p 12345:12345 -p 25333:25333 -p 5005:5005 -d apache/dolphinscheduler-standalone-server:3.1.8下载Dolphinschedule…...