目录
一、:相关概念
1.1中央处理器(CPU,Central Processing Unit)
1.2程序、进程、线程
1.3线程与任务
二、线程的创建:
2.1继承Thread创建线程:
使用Thread子类创建线程的优缺点
2.2实现Runnable接口创建线程:
2.3使用Callable和Future创建线程(了解)
2.4通过实现接口和继承类两种方式创建对象的比较:
2.5在并发编程中存在线程安全问题
三、线程的生命周期:
3.1常见方法
3.2sleep方法:
补充TimeUnit Sleep
3.3interrupt()方法
3.4Joins 方法
3.5yield()方法
3.5wait()和notify()方法
四、线程的同步
4.1volatile关键字
编辑
4.1.5背景
4.2synchronized关键字
4.2.1实例方法
4.2.2同步静态方法
4.2.3同步代码块
4.2.4注意
4.2.5变量的可见性与并发执行操作的原子性
4.2.6Volatile和Synchronization对比
4.3CountDownLatch
五、Lock接口
5.1内部锁(Intrinsic Locks)
5.2原子访问(Atomic Access)
5.3锁对象(Lock Objects)
5.4Executor(执行器)
5.5线程池(Thread Pools)
5.6原子变量(Atomic Variables)
5.7线程局部变量(ThreadLocal)
5.8并发集合(Concurrent Collections)
一、:相关概念
1.1中央处理器(CPU,Central Processing Unit)
CPU 是计算机的中央运算单元,用来计算的。它从内存里面读取指令,然后执行
CPU 调度线程来执行任务。在多核 CPU 中,每个核心可以独立调度和执行线程。在单核 CPU 中,操作系统使用时间片轮转机制来实现多线程并发执行
1.2程序、进程、线程
程序:是指含有指令和数据的文件,被存储在磁盘或其他的数据设备中,也就是说程序是静态的代码;
进程:是程序的一次执行过程,是代码在数据集合上的一次运行活动,是系统资源分配和调度的基本单位;
线程:线程是进程中的一个实体,是被系统独立调度和分派的基本单位,一个进程中至少有一个线程,进程中的多个线程共享进程的资源;
简而言之,一个程序至少有一个进程,一个进程至少有一个线程
补充:
进程在运行过程中创建的资源随着进程的终止而被销毁,所使用的系统资源在进程终止时被释放或关闭
1.3线程与任务
线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。一个进程内可以包含多个线程,线程是资源调度的最小单位。线程本质上就是执行任务的对象,如果把线程比作一台打印机,那么任务就是一份需要打印的文档
二、线程的创建:
在 Java 中,有两种方法可以创建线程并提供将在该线程中运行的代码:
提供一个实现了 Runnable 接口的对象。Runnable 接口定义了一个 run 方法,其中包含了线程要执行的代码。
通过继承 Thread 类并重写 run 方法来创建一个子类。
此外,Java 还提供了 Executor 框架,它允许您将任务提交给一个线程池来执行,而不是直接创建新的线程。
2.1继承Thread创建线程:
- 扩展 Thread类,
- 重写父类的run()方法,
- 规定线程的具体操作 调用Thread类的start()方法启动线程
class MyThread extends Thread {
@Override
public void run() {
// 在这里编写线程要执行的代码
System.out.println("MyThread running");
}
}
public class Main {
public static void main(String[] args) {
// 创建一个 MyThread 对象
MyThread myThread = new MyThread();
// 启动线程
myThread.start();
}
}
我们定义了一个名为
MyRunnable
的类,它实现了Runnable
接口。在MyRunnable
类中,我们重写了run
方法,该方法包含了线程要执行的代码。在
main
方法中,我们创建了一个MyRunnable
对象,并使用该对象创建了一个线程。然后我们调用start
方法来启动线程。当线程启动后,它将执行MyRunnable
类中的run
方法。
使用Thread子类创建线程的优缺点
2.2实现Runnable接口创建线程:
- 使用Thread创建线程对象时,使用的构造方法:
- Thread(Runnable target)
- Thread(Runnable target,String name)
- 该构造方法中的参数是一个Runnable类型的接口,因此,在创建线程对象时必须向构造方法的参数传递一个实现Runnable接口类的实例,该实例对象称作所创线程的目标对象,当线程调用start()方法后,一旦轮到它来享用CPU资源,目标对象就会自动调用接口中的run()方法(接口回调)
- Runnable接口只有一个抽象方法run()
class MyRunnable implements Runnable {
@Override
public void run() {
// 在这里编写线程要执行的代码
System.out.println("MyRunnable running");
}
}
public class Main {
public static void main(String[] args) {
// 创建一个实现了 Runnable 接口的对象
MyRunnable myRunnable = new MyRunnable();
// 使用该对象创建一个线程
Thread thread = new Thread(myRunnable);
// 启动线程
thread.start();
}
}
我们定义了一个名为
MyThread
的类,它继承了Thread
类。在MyThread
类中,我们重写了run
方法,该方法包含了线程要执行的代码。在
main
方法中,我们创建了一个MyThread
对象,并调用start
方法来启动线程。当线程启动后,它将执行MyThread
类中的run
方法。
2.3使用Callable和Future创建线程(了解)
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;
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
// 在这里编写线程要执行的代码
int result = 0;
for (int i = 1; i <= 100; i++) {
result += i;
}
return result;
}
}
public class Main {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 创建一个线程池
ExecutorService executor = Executors.newSingleThreadExecutor();
// 创建一个 Callable 对象
MyCallable myCallable = new MyCallable();
// 提交 Callable 对象并获取 Future 对象
Future<Integer> future = executor.submit(myCallable);
// 获取任务的结果
int result = future.get();
System.out.println("Result: " + result);
// 关闭线程池
executor.shutdown();
}
}
首先,我们定义了一个
MyCallable
类,它实现了Callable
接口。在call()
方法中,我们编写了线程要执行的代码。在这个例子中,线程计算了 1 到 100 的和。然后,在
main
方法中,我们创建了一个单线程的线程池,并创建了一个MyCallable
对象。接着,我们使用executor.submit(myCallable)
方法提交了这个Callable
对象,并获得了一个Future
对象。最后,我们调用future.get()
方法来获取任务的结果。注意,在调用
future.get()
方法时,主线程会阻塞,直到任务完成并返回结果为止。最后,我们调用executor.shutdown()
方法来关闭线程池。
2.4通过实现接口和继承类两种方式创建对象的比较:
采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。还便于目标对象的共享
使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。不能再继承其他类
2.5在并发编程中存在线程安全问题
主要原因有:
- 存在共享数据
- 多线程共同操作共享数据。
三、线程的生命周期:
3.1常见方法
Thread
类提供了许多用于管理线程的方法。下面是一些常用的方法:
start()
: 启动一个新线程并执行run()
方法中的代码。run()
: 定义线程要执行的代码。sleep(long millis)
: 使当前线程暂停执行指定的时间。join()
: 等待该线程终止。interrupt()
: 中断该线程。isInterrupted()
: 判断该线程是否被中断。currentThread()
: 返回当前正在执行的线程对象。
3.2sleep方法:
- Thread类提供,基于毫秒与基于纳秒的暂停时间
- sleep时间并不能保证准确,它们受底层操作系统设施的限制(大于等于sleep时间,继续执行,无法精确)
- sleep周期可以通过中断(Interrupt)来终止
// 定义一个类,继承 Thread 类
class MyThread extends Thread {
// 重写 run 方法,定义线程要执行的代码
@Override
public void run() {
// 循环打印 1 到 10 的数字
for (int i = 1; i <= 10; i++) {
System.out.println(i);
// 每次打印后暂停 1 秒钟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Thread was interrupted");
}
}
}
}
public class Main {
public static void main(String[] args) {
// 创建一个 MyThread 对象
MyThread myThread = new MyThread();
// 调用 start 方法来启动这个新线程
myThread.start();
}
}
补充TimeUnit Sleep
java.util.concurrent.TimeUnit
是一个枚举类,它提供了一组用于表示时间单位的常量。这些常量包括:
NANOSECONDS
: 纳秒,表示 10 的 -9 次方秒。MICROSECONDS
: 微秒,表示 10 的 -6 次方秒。MILLISECONDS
: 毫秒,表示 10 的 -3 次方秒。SECONDS
: 秒。MINUTES
: 分钟,表示 60 秒。HOURS
: 小时,表示 60 分钟。DAYS
: 天,表示 24 小时。
3.3interrupt()方法
中断(Interrupts),表示线程应停止正在执行的操作,并执行其他操作
由程序员决定,线程应如何响应中断
一个线程通过调用指定线程对象的interrupt()方法,发送一个中断通知,以使指定线程中断
被中断线程获取中断通知后,将抛出InterruptedException异常,因此,捕获异常决定中断后的操作
// 定义一个类,继承 Thread 类
class MyThread extends Thread {
// 重写 run 方法,定义线程要执行的代码
@Override
public void run() {
// 循环打印 1 到 10 的数字
for (int i = 1; i <= 10; i++) {
System.out.println(i);
// 每次打印后暂停 1 秒钟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Thread was interrupted");
// 如果线程被中断,退出循环
break;
}
}
}
}
public class Main {
public static void main(String[] args) {
// 创建一个 MyThread 对象
MyThread myThread = new MyThread();
// 调用 start 方法来启动这个新线程
myThread.start();
// 中断这个线程
myThread.interrupt();
}
}
3.4Joins 方法
- join()方法,允许一个线程等待另一个线程的完成
- 导致当前线程暂停执行,直到指定线程终止
- 与sleep相同,通过InterruptedException异常响应中断
// 定义一个类,继承 Thread 类
class MyThread extends Thread {
// 重写 run 方法,定义线程要执行的代码
@Override
public void run() {
// 循环打印 1 到 10 的数字
for (int i = 1; i <= 10; i++) {
System.out.println(i);
// 每次打印后暂停 1 秒钟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Thread was interrupted");
}
}
}
}
public class Main {
public static void main(String[] args) {
// 创建一个 MyThread 对象
MyThread myThread = new MyThread();
// 调用 start 方法来启动这个新线程
myThread.start();
// 等待这个线程结束
try {
myThread.join();
} catch (InterruptedException e) {
System.out.println("Main thread was interrupted");
}
System.out.println("Main thread finished");
}
}
在上面的代码中,我们在
main
方法中调用了myThread.join()
方法来等待这个线程结束。当这个线程结束后,join()
方法会返回,主线程会继续执行。注意,
join()
方法会抛出InterruptedException
异常,因此我们需要使用try-catch
语句来捕获并处理这个异常。
3.5yield()方法
yield():让出一次执行机会,使线程从运行状态转到可运行状态
yield()
方法是Thread
类的一个静态方法,它用于暂停当前正在执行的线程,让出 CPU 时间片给其他线程。不过,需要注意的是,这个方法只是一个提示,它并不能保证当前线程一定会暂停执行。
// 定义一个类,继承 Thread 类
class MyThread extends Thread {
// 线程的名称
private String name;
// 构造方法,用于设置线程的名称
public MyThread(String name) {
this.name = name;
}
// 重写 run 方法,定义线程要执行的代码
@Override
public void run() {
// 循环打印 1 到 10 的数字
for (int i = 1; i <= 10; i++) {
System.out.println(name + ": " + i);
// 如果 i 是 5 的倍数,调用 yield 方法
if (i % 5 == 0) {
Thread.yield();
}
}
}
}
public class Main {
public static void main(String[] args) {
// 创建两个 MyThread 对象
MyThread thread1 = new MyThread("Thread 1");
MyThread thread2 = new MyThread("Thread 2");
// 调用 start 方法来启动这两个新线程
thread1.start();
thread2.start();
}
}
我们定义了一个
MyThread
类,它继承了Thread
类。在run()
方法中,我们编写了线程要执行的代码。在这个例子中,线程会打印 1 到 10 的数字,并在每次打印到 5 的倍数时调用yield()
方法。然后,在
main
方法中,我们创建了两个MyThread
对象,并调用了它们的start()
方法来启动这两个新线程。当这两个线程运行时,它们会交替打印数字,并在打印到 5 的倍数时调用yield()
方法。
3.5wait()和notify()方法
wait()方法的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,当前线程被唤醒(进入“就绪状态”)
notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程
class MyThread extends Thread {
private final Object lock = new Object();
@Override
public void run() {
synchronized (lock) {
for (int i = 1; i <= 10; i++) {
System.out.println(i);
try {
Thread.sleep(1000);
if (i == 5) {
// 当 i 等于 5 时,调用 lock.wait() 方法来使当前线程等待
lock.wait();
}
} catch (InterruptedException e) {
System.out.println("Thread was interrupted");
}
}
}
}
public void wakeUp() {
synchronized (lock) {
// 调用 lock.notify() 方法来唤醒等待的线程
lock.notify();
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
Thread.sleep(9000);
// 调用 myThread.wakeUp() 方法来唤醒等待的线程
myThread.wakeUp();
}
}
四、线程的同步
4.1volatile关键字
volatile 是一个 Java 关键字,它用于修饰变量。当一个变量被声明为 volatile 时,它表示这个变量是易变的,可能会被多个线程同时访问和修改。
volatile 关键字可以保证变量的可见性和有序性。可见性指的是当一个线程修改了一个 volatile 变量时,其他线程能够立即看到这个修改。有序性指的是禁止编译器对 volatile 变量进行指令重排。
// 定义一个类,继承 Thread 类
class MyThread extends Thread {
// 使用 volatile 关键字修饰变量
private volatile boolean running = true;
// 重写 run 方法,定义线程要执行的代码
@Override
public void run() {
// 循环打印 1 到 10 的数字
for (int i = 1; i <= 10 && running; i++) {
System.out.println(i);
// 每次打印后暂停 1 秒钟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Thread was interrupted");
}
}
}
public void stopRunning() {
running = false;
}
}
public class Main {
public static void main(String[] args) {
// 创建一个 MyThread 对象
MyThread myThread = new MyThread();
// 调用 start 方法来启动这个新线程
myThread.start();
// 停止这个线程
myThread.stopRunning();
}
}
4.1.5背景
线程主要通过访问共享数据实现通信 这种通信形式非常有效,但会产生两种错误:线程冲突与内存一致性的错误。防止这些错误所需的工具是同步
4.2synchronized关键字
synchronized
是一个 Java 关键字,它用于修饰方法或代码块。当一个方法或代码块被声明为synchronized
时,它表示这个方法或代码块是同步的,只能被一个线程同时访问。
synchronized
关键字可以保证线程安全,防止多个线程同时访问和修改共享数据。当一个线程进入synchronized
方法或代码块时,它会获得一个锁;当这个线程离开synchronized
方法或代码块时,它会释放这个锁。只有获得锁的线程才能进入synchronized
方法或代码块,其他线程必须等待锁被释放。
- 同步普通方法(实例方法)上锁,锁是当前实例对象 ,进入方法前要获得当前实例的锁,方法执行完释放。
- 同步静态方法,锁是当前类的class对象 ,进入方法前前要获得当前类对象的锁,方法执行完释放。
- 同步代码块,要指定锁的对象,是可以是实例对象,也可以是类对象,进入同步代码块前要获得给定对象的锁,代码块执行完释放
4.2.1实例方法
public class SynchronizedExample {
private int count = 0; // 定义一个私有变量count
public synchronized void incrementCount() { // 使用synchronized关键字修饰实例方法
count++; // 每次调用方法时,count自增1
}
public void runExample() {
Thread thread1 = new Thread(new Runnable() { // 创建线程1
@Override
public void run() {
for (int i = 0; i < 10000; i++) { // 循环10000次
incrementCount(); // 调用incrementCount方法
}
}
});
Thread thread2 = new Thread(new Runnable() { // 创建线程2
@Override
public void run() {
for (int i = 0; i < 10000; i++) { // 循环10000次
incrementCount(); // 调用incrementCount方法
}
}
});
thread1.start(); // 启动线程1
thread2.start(); // 启动线程2
try {
thread1.join(); // 等待线程1执行完毕
thread2.join(); // 等待线程2执行完毕
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Count: " + count); // 输出count的值
}
public static void main(String[] args) {
SynchronizedExample example = new SynchronizedExample();
example.runExample();
}
}
这个程序创建了两个线程,每个线程都会调用
incrementCount
方法10000次。由于该方法使用synchronized
关键字修饰,因此每次只能有一个线程访问该方法。这样可以确保count
变量的值在程序结束时为20000。
4.2.2同步静态方法
public class SynchronizedStaticExample {
private static int count = 0; // 定义一个私有静态变量count
public static synchronized void incrementCount() { // 使用synchronized关键字修饰静态方法
count++; // 每次调用方法时,count自增1
}
public void runExample() {
Thread thread1 = new Thread(new Runnable() { // 创建线程1
@Override
public void run() {
for (int i = 0; i < 10000; i++) { // 循环10000次
incrementCount(); // 调用incrementCount方法
}
}
});
Thread thread2 = new Thread(new Runnable() { // 创建线程2
@Override
public void run() {
for (int i = 0; i < 10000; i++) { // 循环10000次
incrementCount(); // 调用incrementCount方法
}
}
});
thread1.start(); // 启动线程1
thread2.start(); // 启动线程2
try {
thread1.join(); // 等待线程1执行完毕
thread2.join(); // 等待线程2执行完毕
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Count: " + count); // 输出count的值
}
public static void main(String[] args) {
SynchronizedStaticExample example = new SynchronizedStaticExample();
example.runExample();
}
}
在
incrementCount
方法中,我们使用了一个synchronized
代码块来同步对类对象SynchronizedStaticExample2.class
的访问。这样可以确保每次只能有一个线程访问该代码块,从而避免了多线程环境下对共享变量count
的竞争条件
4.2.3同步代码块
4.2.4注意
构造函数不能同步,在构造函数中使用synchronized关键字是语法错误。同步构造函数没有意义,因为只有创建对象的线程在构建时才能访问它,多线程调用将创建多个对象
4.2.5变量的可见性与并发执行操作的原子性
变量的可见性指的是当一个线程修改了一个共享变量的值后,其他线程能否立即看到这个修改。如果不能立即看到,那么这个变量就不具有可见性。Java 提供了一些机制来保证变量的可见性,例如使用
volatile
关键字修饰变量、使用同步块或方法等。并发执行操作的原子性指的是在多线程环境下,对共享变量的读写操作能够以原子方式完成,即不会被其他线程中断。如果一个操作不具有原子性,那么它可能会被其他线程中断,导致数据不一致等问题。Java 提供了一些机制来保证操作的原子性,例如使用同步块或方法、使用原子变量类等。
4.2.6Volatile和Synchronization对比
4.3CountDownLatch
CountDownLatch
是 Java 中的一个同步工具类,它允许一个或多个线程等待,直到一组操作完成。CountDownLatch
维护一个计数器,当计数器的值为 0 时,所有等待的线程都将被释放。
import java.util.concurrent.CountDownLatch;
class MyThread extends Thread {
private CountDownLatch latch;
public MyThread(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
// 循环打印 1 到 10 的数字
for (int i = 1; i <= 10; i++) {
System.out.println(i);
// 每次打印后暂停 1 秒钟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("Thread was interrupted");
}
}
// 递减计数器的值
latch.countDown();
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
// 创建一个 CountDownLatch 对象并指定计数器的初始值为 1
CountDownLatch latch = new CountDownLatch(1);
// 创建一个 MyThread 对象并启动这个新线程
MyThread myThread = new MyThread(latch);
myThread.start();
// 等待计数器的值变为 0
System.out.println("Waiting for thread to finish");
latch.await();
System.out.println("Thread is done");
}
}
在这个示例中,我们创建了一个
CountDownLatch
对象并指定了计数器的初始值为 1。然后我们启动了一个新线程,并在该线程中调用latch.countDown()
方法来递减计数器的值。在主线程中,我们调用latch.await()
方法来等待计数器的值变为 0。当新线程运行完毕并调用
countDown()
方法后,计数器的值将变为 0,主线程将从await()
方法返回并继续执行。
五、Lock接口
5.1内部锁(Intrinsic Locks)
内部锁(Intrinsic Locks),也称为监视器锁(Monitor Locks),是 Java 中每个对象都具有的一种锁。当一个线程进入一个同步块或方法时,它会自动获取该对象的内部锁。当线程离开同步块或方法时,它会自动释放该对象的内部锁。
内部锁用于实现线程同步,确保在同一时间只有一个线程能访问共享资源
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
在这个示例中,
increment
和getCount
方法都被声明为synchronized
。这意味着当一个线程调用这些方法时,它会自动获取Counter
对象的内部锁。由于同一时间只有一个线程能持有内部锁,因此这些方法在同一时间只能被一个线程调用。
5.2原子访问(Atomic Access)
原子访问(Atomic Access)指的是在多线程环境下,对变量的读写操作能够以原子方式完成,即不会被其他线程中断。Java 提供了一些原子变量类,如
AtomicInteger
、AtomicLong
和AtomicReference
等,它们能够保证对变量的读写操作是原子的。原子性操作:要么完全执行,要么不执行 对于引用变量和大多数基本变量(long和double超过32bit在某些虚拟机按2次读写),读取和写入均为原子性操作
5.3锁对象(Lock Objects)
锁对象(Lock Objects)是 Java 提供的一种用于实现线程同步的工具。它提供了比内部锁(Intrinsic Locks)更灵活的锁定机制,允许线程以更细粒度的方式来控制对共享资源的访问。
Lock,是用于控制多线程访问共享资源的工具,提供对共享资源的独占访问权限:一次只有一个线程可以获取该锁,并且对共享资源的访问首先获取该锁
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Counter {
private int count = 0;
// 创建一个 ReentrantLock 对象
private Lock lock = new ReentrantLock();
public void increment() {
// 获取锁
lock.lock();
try {
// 递增 count 的值
count++;
} finally {
// 释放锁
lock.unlock();
}
}
public int getCount() {
// 获取锁
lock.lock();
try {
// 返回 count 的值
return count;
} finally {
// 释放锁
lock.unlock();
}
}
}
public class Main {
public static void main(String[] args) {
// 创建一个 Counter 对象
Counter counter = new Counter();
// 调用 increment 方法来递增 count 的值
counter.increment();
// 调用 getCount 方法来获取 count 的当前值并将其打印出来
System.out.println(counter.getCount());
}
}
在这个示例中,我们定义了一个名为 Counter 的类,该类使用 ReentrantLock 对象来保护对其 count 变量的访问。Counter 类有两个方法:increment() 和 getCount()。increment() 方法用于递增 count 的值,而 getCount() 方法用于获取 count 的当前值。在 Main 类的 main 方法中,创建了一个 Counter 对象并调用了它的 increment() 和 getCount() 方法。
5.4Executor(执行器)
提供用于管理/产生用于跟踪一个或多个异步任务进度的方法
java.util.concurrent包,定义了三个Executor接口:
- Executor,支持启动新任务的简单接口 ExecutorService,
- Executor子接口,增加了线程的生命周期管理特性
- ScheduledExecutorService,ExecutorService子接口,支持Future/定期执行任务
5.5线程池(Thread Pools)
线程池(Thread Pools)是一种常用的并发模式,它可以帮助我们更好地管理线程资源。在线程池中,一组固定大小的线程被创建并等待执行任务。当有新任务到来时,线程池中的一个线程会被分配执行该任务。任务执行完成后,该线程会返回线程池等待下一个任务。
5.6原子变量(Atomic Variables)
原子变量(Atomic Variables)是一种特殊类型的变量,它可以在多线程环境下安全地进行读写操作。Java 中的 java.util.concurrent.atomic 包提供了一些原子变量类,包括 AtomicInteger、AtomicLong、AtomicBoolean 和 AtomicReference。这些类分别表示可以原子更新的 int、long、boolean 和对象引用。
所有的原子变量类都有 get 和 set 方法,它们的工作方式类似于对 volatile 变量的读写操作。也就是说,set 操作与同一变量上任何后续 get 操作之间具有 happens-before 关系。原子 compareAndSet 方法也具有这些内存一致性特性,简单的原子算术方法也适用于整数原子变量
5.7线程局部变量(ThreadLocal)
ThreadLocal是
java.lang
包中的一个构造,它允许您存储仅能被特定线程访问的数据。它提供了线程局部变量,这些变量与它们的普通副本不同,每个访问一个线程局部变量(通过其get
或set
方法)的线程都有其自己独立初始化的变量副本。java.lang.ThreadLocal<T>类,提供线程局部变量
5.8并发集合(Concurrent Collections)
Concurrent Collections是一组线程安全的集合类,它们可以在多线程环境中安全地使用。在Java中,
java.util.concurrent
包中包含了许多对Java集合框架的补充,这些补充最容易通过提供的集合接口进行分类。例如,BlockingQueue定义了一个先进先出的数据结构,当您尝试向满队列添加或从空队列检索时会阻塞或超时;ConcurrentMap是java.util.Map的子接口,它定义了有用的原子操作。