> 文章列表 > JAVA入坑之线程

JAVA入坑之线程

JAVA入坑之线程

目录

一、:相关概念

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 中,有两种方法可以创建线程并提供将在该线程中运行的代码:

  1. 提供一个实现了 Runnable 接口的对象。Runnable 接口定义了一个 run 方法,其中包含了线程要执行的代码。

  2. 通过继承 Thread 类并重写 run 方法来创建一个子类。

此外,Java 还提供了 Executor 框架,它允许您将任务提交给一个线程池来执行,而不是直接创建新的线程。

2.1继承Thread创建线程:

  • 扩展 Thread类,
  • 重写父类的run()方法,
  • 规定线程的具体操作 调用Thread类的start()方法启动线程
class MyThread extends Thread {@Overridepublic 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 {@Overridepublic 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> {@Overridepublic 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在并发编程中存在线程安全问题

主要原因有:

  1. 存在共享数据
  2. 多线程共同操作共享数据。

三、线程的生命周期:

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 方法,定义线程要执行的代码@Overridepublic 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 方法,定义线程要执行的代码@Overridepublic 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 方法,定义线程要执行的代码@Overridepublic 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 方法,定义线程要执行的代码@Overridepublic 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();@Overridepublic 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 方法,定义线程要执行的代码@Overridepublic 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 方法或代码块,其他线程必须等待锁被释放。

  1. 同步普通方法(实例方法)上锁,锁是当前实例对象 ,进入方法前要获得当前实例的锁,方法执行完释放。
  2. 同步静态方法,锁是当前类的class对象 ,进入方法前前要获得当前类对象的锁,方法执行完释放。
  3. 同步代码块,要指定锁的对象,是可以是实例对象,也可以是类对象,进入同步代码块前要获得给定对象的锁,代码块执行完释放

 

4.2.1实例方法

public class SynchronizedExample {private int count = 0; // 定义一个私有变量countpublic synchronized void incrementCount() { // 使用synchronized关键字修饰实例方法count++; // 每次调用方法时,count自增1}public void runExample() {Thread thread1 = new Thread(new Runnable() { // 创建线程1@Overridepublic void run() {for (int i = 0; i < 10000; i++) { // 循环10000次incrementCount(); // 调用incrementCount方法}}});Thread thread2 = new Thread(new Runnable() { // 创建线程2@Overridepublic void run() {for (int i = 0; i < 10000; i++) { // 循环10000次incrementCount(); // 调用incrementCount方法}}});thread1.start(); // 启动线程1thread2.start(); // 启动线程2try {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; // 定义一个私有静态变量countpublic static synchronized void incrementCount() { // 使用synchronized关键字修饰静态方法count++; // 每次调用方法时,count自增1}public void runExample() {Thread thread1 = new Thread(new Runnable() { // 创建线程1@Overridepublic void run() {for (int i = 0; i < 10000; i++) { // 循环10000次incrementCount(); // 调用incrementCount方法}}});Thread thread2 = new Thread(new Runnable() { // 创建线程2@Overridepublic void run() {for (int i = 0; i < 10000; i++) { // 循环10000次incrementCount(); // 调用incrementCount方法}}});thread1.start(); // 启动线程1thread2.start(); // 启动线程2try {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;}@Overridepublic 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 对象并指定计数器的初始值为 1CountDownLatch latch = new CountDownLatch(1);// 创建一个 MyThread 对象并启动这个新线程MyThread myThread = new MyThread(latch);myThread.start();// 等待计数器的值变为 0System.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 提供了一些原子变量类,如 AtomicIntegerAtomicLong 和 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包中的一个构造,它允许您存储仅能被特定线程访问的数据。它提供了线程局部变量,这些变量与它们的普通副本不同,每个访问一个线程局部变量(通过其getset方法)的线程都有其自己独立初始化的变量副本。

java.lang.ThreadLocal<T>类,提供线程局部变量

5.8并发集合(Concurrent Collections)

Concurrent Collections是一组线程安全的集合类,它们可以在多线程环境中安全地使用。在Java中,java.util.concurrent包中包含了许多对Java集合框架的补充,这些补充最容易通过提供的集合接口进行分类。例如,BlockingQueue定义了一个先进先出的数据结构,当您尝试向满队列添加或从空队列检索时会阻塞或超时;ConcurrentMap是java.util.Map的子接口,它定义了有用的原子操作。